/*
 *
 * COPYRIGHT NVIDIA CORPORATION 2007. ALL RIGHTS RESERVED.  
 * BY ACCESSING OR USING THIS SOFTWARE, YOU AGREE TO: 
 *
 *  1) ACKNOWLEDGE NVIDIA'S EXCLUSIVE OWNERSHIP OF ALL RIGHTS 
 *     IN AND TO THE SOFTWARE; 
 *
 *  2) NOT MAKE OR DISTRIBUTE COPIES OF THE SOFTWARE WITHOUT 
 *     INCLUDING THIS NOTICE AND AGREEMENT; 
 *
 *  3) ACKNOWLEDGE THAT TO THE MAXIMUM EXTENT PERMITTED BY 
 *     APPLICABLE LAW, THIS SOFTWARE IS PROVIDED *AS IS* AND 
 *     THAT NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, 
 *     EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED
 *     TO, IMPLIED WARRANTIES OF MERCHANTABILITY  AND FITNESS 
 *     FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS BE LIABLE FOR ANY 
 * SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES 
 * WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS 
 * OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS 
 * INFORMATION, OR ANY OTHER PECUNIARY LOSS), INCLUDING ATTORNEYS'
 * FEES, RELATING TO THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 */

/*
 *
 * NVidia Corporation
 *
 * Written by Ryan Geiss
 * 
 */

/*
  Note that in this demo, the blocks (as they are called in the 
  GPU Gems 3 chapter) are referred to as "chunks."  Also note that 
  the highest-res chunks in the code are 4.0 world space units to 
  a side, as opposed to 1.0, as described in the chapter.  There are 
  3 levels of detail (LODs): 0 is the highest, and 1 and 2 are 
  progressively lower levels of detail (i.e. larger-sized chunks 
  in world space).

  Check out bin\args.txt for various startup options you can adjust
  for the demo, including the gridsize for the chunks ("VoxelDim"),
  window resolution, and so on.

  Build requirements:
    1. Windows Vista
    2. DirectX 10 SDK
    3. A DX10-capable GPU
    4. Visual Studio 2005
    5. Visual Studio must have the paths for the include and lib files 
        for the installed DX10 SDK added to the its list of global include & lib 
        directories.  Go to Tools -> Options -> Projects and Solutions -> VC++ 
        Directories.
  
  Build process:
    1. Load TerrainDemo.sln.
    2. Set to the 'Debug' or 'Release' configuration.
    3. Rebuild all.
    4. In the solution explorer, right click the 'TerrainDemo' project and
        'Set as StartUp Project'.
    5. Also right-click it and go to Properties; then go to the Debugging
        tab.  Where it says 'Working Directory', paste in the full path
        to your "TerrainDemo\executable\bin" directory.  Then switch to
        the other configuration (Debug vs. Release) and set it for that
        configuration as well.
    6. Run it.  If it says "fmod.dll not found" then your working directory
        is incorrect; go back to step 5.

  To see where the many chunks are managed, scroll down to OnRenderPass().
  To see where a single chunk is built, scroll down to BuildLodChunkNOW().
  To see the main function that is run once per frame, scroll down to Update().
  The main files are TerrainDemo.cpp, TerrainDemo.h, and Init.cpp.

*/

#define _WIN32_WINNT 0x500

#include <NRender/NRWindow.h>
#include <NRender/dx10/NRWindowDX10.h>
#include <NCommon/NReader.h>
#include <NCommon/NSplashScreen.h>
#include <NImage/NImageUtils.h>
#include <NImage/NTarga.h>

#include <NVDemo/NVMenu.h>
#include <NVDemo/NVMesh.h>
#include <NVDemo/NVNavigator.h>
#include <NVDemo/NVPickContext.h>
#include <NVDemo/NVScene.h>
#include <NVDemo/NVShader.h>
#include <NVDemo/NVStockObjects.h>
#include <NVDemo/NVViewer.h>
#include <NRender/NRVertexBuffer.h>
#include <NRender/dx10/NRTextureDX10.h>
#include <NRender/dx10/NRenderDX10.h>
#include <NRender/dx10/NRVertexBufferDX10.h>
#include <NRender/NRShader.h>

#include "helpers/NVAssist.h"

#include <windows.h>
#include <winuser.h>
#include <strstream>
#include <fstream>

#include <commctrl.h>

#include "TerrainDemo.h"

#include ".\helpers\ReloadShaders.h"
#include <NHelper\NVSliderManager.h>

#include "..\resource.h"

#define CHUNK_FADEIN_SECONDS  (  0.5f  )  //TWEAK
#define BURNIN_DELAY      600.0f  // # of seconds until logo + "click here for help" fade (prevent burn-in)
#define DEFAULT_CAMERA_DISTANCE 1.0f
#define MOUSE_CURSOR_HIDE_TIME (0.4)   // in seconds
#define SLOW_MOTION_SPEED_DIV (7.0f)
#define SECONDS_TO_FADE_IN_HELP_SCREEN (0.2f)
#define SECONDS_TO_SHOW_HELP_SCREEN    (12.0f)
#define SECONDS_TO_FADE_IN_ABOUT_BOX   (0.2f)
#define SECONDS_TO_SHOW_ABOUT_BOX      (24.0f)

TerrainDemo *TerrainDemo::s_instance = NULL;
TerrainDemo* g_instance = NULL;
HINSTANCE g_hInstance = NULL;

NVec4f g_fDebugVal = NVec4f(1,1,1,1);

//---------------------
// sliders:
int g_nSliders = 0;
NVConstantBuffer* g_SliderCB = NULL;
std::vector<NVSlider*> g_nvsliders;
std::map<NVSlider*,int> g_sliderParamMap;
// to iterate on this:
//  std::map<NVSlider *, NVFloatParam *>::iterator it;
//  for (it = g_constParamMap.begin(); it != g_constParamMap.end(); it++) { ... }
const float  g_spacing   = 0.023f;
const NVec2f g_size      = NVec2f( 0.23f, 0.021f );
const NColor4f g_TextColor = NColor4f(1,1,1,1);//NVec3f( 0.4f, 0.4f, 0.5f );
      NPnt2f g_startPos  = NPnt2f( 0.015f, 0.98f );// - g_size.y*(sizeof(g_sliders)/sizeof(SliderInfo)+3) );
//---------------------

// slider callback
static void SliderCallback(NVSlider* slider, float t) 
{
  // for when user MANUALLY clicks a slider.  t is in 0..1 range.
  assert(slider);
  int index = g_sliderParamMap[slider];

  NVFloatParam* param = dynamic_cast<NVFloatParam*>(g_SliderCB->GetConstants()[index]);
  assert(param);
  
  NPnt4f oldVal = param->GetValue();
  param->SetValue( NPnt4f(param->GetMin().x + t*(param->GetMax().x - param->GetMin().x), oldVal.y, oldVal.z, oldVal.w) );

  //if (param->GetParamName() == "diffuse_light" || param->GetParamName() == "spec_light" || param->GetParamName() == "light_wrap") 
  //  g_instance->m_bLightCubesOutOfDate = true;
}

void TerrainDemo::InitMenuAndSliders(NVScene* scene)
{
    // start with a top level menu object and add it to the viewer
    m_pTopMenu = new NVStaticMenu;
    scene->AppendMenus( m_pTopMenu );
    m_viewer->SetMainMenu( m_pTopMenu );
    m_pTopMenu->SetVisible( true );

    // add a listener to the mouse mananger so that we get mouse events
    NVMM->AddListener( m_pTopMenu );  

    // add sliders submenu
    m_pSliderMenu = new NVStaticMenu();
    m_pTopMenu->AppendMenuItems( m_pSliderMenu );
    m_pSliderMenu->SetVisible( false );  // not visible til user hits 's'

    g_SliderCB = dynamic_cast<NVConstantBuffer*>(NVNAMESPACE->GetByName("SliderCB")); assert(g_SliderCB);

    g_nSliders = g_SliderCB->GetConstants().size();
      g_startPos.y -= g_size.y*(g_nSliders+1);

    NV2DTexture* sliderTex = dynamic_cast<NV2DTexture*>(NVNAMESPACE->GetByName("sliderBar"));
    NV2DTexture* knobTex   = dynamic_cast<NV2DTexture*>(NVNAMESPACE->GetByName("sliderBtn"));

    // set up the sliders
    for (int i=0; i<g_nSliders; i++) 
    {
      NVFloatParam* param = dynamic_cast<NVFloatParam*>(g_SliderCB->GetConstants()[i]);
      assert(param);

      // copy the param's initial (default) value (in .x) to .w as a backup.
      // (we'll copy from .w to .x if they hit 'panic')
      NPnt4f val = param->GetValue();
      param->SetValue( NPnt4f( val.x, val.y, val.z, val.x ) );

      // add slider and callback:
      NVSlider *slider = new NVSlider();
        g_nvsliders.push_back(slider);
        m_pSliderMenu->AppendMenuItems( slider );
        slider->SetVisible( true );
        slider->SetCallback( param->GetParamName() );
        slider->SetValue( (param->GetValue().x-param->GetMin().x)/(param->GetMax().x-param->GetMin().x)     );//param->GetValue().x );
        NPnt2f pos = g_startPos;
        pos.y += g_spacing * i;//g_sliders[i].drawIndex;
        slider->SetPosition(pos);
        slider->SetSize(g_size);
        slider->SetTextColor( g_TextColor );
        slider->SetDrawText( true );
        slider->SetName( param->GetParamName() ); // name the slider nodes something unique to avoid the damn rename warnings
        slider->m_MinMaxAware = true;
	      slider->m_min = param->GetMin().x;
	      slider->m_max = param->GetMax().x;
        slider->SetSlider(sliderTex);
        slider->SetKnob  (knobTex  );
      NVMM->AddCallback( param->GetParamName(), (void *)SliderCallback );
	    g_sliderParamMap[slider] = i;//constParam;
    }
}


bool TestDisplayMode(uint w, uint h, uint bpp) {
  int i = 0;
  DEVMODE dm;
  while (EnumDisplaySettings(NULL, i, &dm)) {
    if (dm.dmPelsWidth == w && dm.dmPelsHeight == h && dm.dmBitsPerPel == bpp)
      return true;
    i++;
  }
  return false;
}

void FindBestFullscreenDisplayMode(int *w, int *h, int *rw, int *rh, int bpp)
{
  // this code intelligently finds the best display mode for the demo to run in,
  //   up to a max of the old value of *w (win_width) if it is nonzero.
  // if user was in a horizontal or vertical span (2 displays) one will be shut off.
  // if user has a true widescreen display it will pick a true widescreen mode.
  //   otherwise it will pick a 4:3 or 5:4 mode (whichever is native) and 
  //   crop the rendering area for "fake widescreen mode".

  uint old_w = *w;
  uint old_h = *h;

  // figure out current aspect ratio, so we have some idea of what mode the user
  // is running in (horz/vert span, true widescreen, 4:3, or 5:4)
  DEVMODE dm;
  dm.dmSize = sizeof(DEVMODE);
  if (!EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm)) 
    return;
  uint user_w = dm.dmPelsWidth;
  uint user_h = dm.dmPelsHeight;
  float user_aspect = user_w/(float)user_h;
  // horz. span modes: aspect >= 2.5
  //    2560x1024:  2.5
  //    2560x 960:  2.666
  // vert. span modes: aspect <= 0.7
  //    1280x2048:  0.625
  //    1280x1920:  0.666
  // true widescreen displays: 
  //    1920x1080:  1.777
  //    1280x 720:  1.777
  // 5:4 LCD:
  //    1280x1024:  1.25
  // 4:3 CRT or LCD:
  //    1280x 960:  1.333

  uint new_w = 800;  // fallback
  uint new_h = 600;  // fallback
  bool bTrueWidescreen = false;
  bool bNative5to4 = (user_aspect > 2.4f && user_aspect < 2.6f) ||  // 5:4 horz span
                     (user_aspect < 0.65f) ||                      // 5:4 vert span
                     (user_aspect > 1.21f && user_aspect < 1.29f);  // 5:4 single display

  // here are a couple different ordered lists of display modes to try, for different scenarios:
  int wh_pairs_to_test_16x9[] = {
    // try 16:9 modes; do largest first, so we have best chance of hitting max. HW resolution
    2560, 1600,
    1920, 1200,    
    1920, 1080,    
    1680, 1050,
    1680, 1024,
    1600, 1024,    
    1440,  900,    // some VTBook laptop
    1360,  765,    // some VTBook laptop
    1360,  768,    // some VTBook laptop
    1366,  768,    
    1280,  768,    // for most widescreen desktops
    1280,  800,    // seen it on a widescreen laptop
    1280,  720,    // just an extra backup (ideal ratio)
    1024,  600,    // seen it on a widescreen laptop
    1024,  576,    // just an extra backup (ideal ratio)
    1024,  640,    // some VTBook laptop
    1024,  512, 
    user_w, user_h,  // this had better work!
    // otherwise default to 1024x768
  };
  int wh_pairs_to_test_5x4[] = {
    // try 5:4 modes first
    2048,  1536,   // not 5:4 but it's at least crisper than the 800x600 fallback
    1600,  1280,   // 5:4
    1600,  1200,   // not 5:4 but it's at least crisper than the 800x600 fallback
    1280,  1024,   // 5:4
    1280,   960,   // not 5:4 but it's at least crisper than the 800x600 fallback
    1152,   864,   // not 5:4 but it's at least crisper than the 800x600 fallback
    1024,   768,   // not 5:4 but it's at least crisper than the 800x600 fallback
    // otherwise default to 800x600
  };               
  int wh_pairs_to_test_4x3[] = {
    // try 4:3 modes first
    2400,  1800,   // 4:3 
    1920,  1440,   // 4:3 
    1600,  1200,   // 4:3 
    1400,  1050,   // 4:3
    1366,  1024,   // 4:3
    1280,   960,   // 4:3 
    1152,   864,   // 4:3 
    1024,   768,   // 4:3 
    // otherwise default to 800x600
  };

  // pick a list to go with, based on what their old display mode was:
  int* pModes = NULL;
  int  nModes = 0;
  if (user_aspect > 1.5f && user_aspect < 2.4f) 
  {
    // true 16:9 display -> go with closest supported res
    bTrueWidescreen = true;
    pModes = wh_pairs_to_test_16x9;
    nModes = sizeof(wh_pairs_to_test_16x9) / (2 * sizeof(int));
  }
  else {
    // this code is for horz. span, vert. span, or a single NON-WIDESCREEN display.
    // in the horz/vert spans, setting a single-monitor resolution will turn off the other display.
    //  (warning though: 1280x1024 isn't always supported there, even though 2560x1024 might be! - driver bug?)
    if (bNative5to4) {
      pModes = wh_pairs_to_test_5x4;
      nModes = sizeof(wh_pairs_to_test_5x4) / (2 * sizeof(int));
    }
    else {
      pModes = wh_pairs_to_test_4x3;
      nModes = sizeof(wh_pairs_to_test_4x3) / (2 * sizeof(int));
    }
  }

  // try the modes in the list
  //  test against available video modes, 
  //  AND screen out 
  int max_width = old_w ? old_w : 99999;
  int i;
  for (i=0; i<nModes; i++) {
    int test_w = pModes[i*2  ];
    int test_h = pModes[i*2+1];
    if ( test_w <= max_width  &&  TestDisplayMode(test_w, test_h, bpp) ) { 
      new_w = test_w; 
      new_h = test_h; 
      break;
    } 
  }
  // if we were shooting for true 16x9 and found nothing, stay at 1024x768 and we ditch the idea of a "true" 16:9 display mode.
  if (bTrueWidescreen && i==nModes) 
    bTrueWidescreen = false;

#if 0
  // I don't know if this is a problem on Vista. The driver doesn't yet have spanning mode
  // available. When spanning mode is implemented, TEST this behavior before turning on
  // the following code!

  // There was a problem with the horizontal span mode in windows XP, where if you've got
  // two 1280x1024 screens horizontally spanned and you select 1280x1024 (your native res,
  // presumably) the machine locks up. But 1024x768 on the same setup will work.
  // So, detect the case where the user is running spanned 12x10 screens, AND they are
  // about to use 12x10 as their rez for this demo. And then fall back to 10x7.
  if ( user_w == 2560 && user_h == 1024 && new_w == 1280 && new_h == 1024 )
  {
    new_w = 1024;
    new_h = 768;
  }
#endif

  // apply the new resolution
  *w = new_w;
  *h = new_h;
  *rw = new_w;
  // FIXME: there's no option for letterboxing, so no fake widescreen for now:
  *rh = new_h;//bTrueWidescreen ? new_h : new_w*9/16;     // we assume the monitor at least has square pixels!

  // report what we did
  char buf[512];
  sprintf(buf, "Auto-resolution determination results:\n  user res was %d x %d\n  new res is %d x %d\n  viewing subrect is %d x %d\n", 
    user_w, user_h, *w, *h, *rw, *rh);
  NOutputString(buf);
}

INT_PTR CALLBACK TerrainDemo::ConfigProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) //DLGPROC
{
  if (uMsg==WM_INITDIALOG)
  { 

    InitCommonControls();

    s_instance->m_hWndConfigDialog = hwndDlg;

    // apply config panel settings:
    CheckDlgButton(hwndDlg, IDC_USEDEFAULT, !(s_instance->m_bUserOverride));
    CheckDlgButton(hwndDlg, IDC_VSYNC, s_instance->m_bVSync);
    CheckDlgButton(hwndDlg, IDC_SHOWHELP, s_instance->m_bHelpButton);

    // DISPLAY MODE COMBO BOX:
    {
      HWND ctrl = GetDlgItem(hwndDlg, IDC_DISPMODE);

      // clear stuff
      SendMessage( ctrl, CB_RESETCONTENT, 0, 0);
      
      char buf[256];
      DEVMODE dm;
      dm.dmSize = sizeof(dm);
      int i = 0;
      while (EnumDisplaySettings(NULL, i++, &dm))
      {
        if (dm.dmBitsPerPel==32 && dm.dmPelsHeight >= 240)
        {
          sprintf(buf, "  %4d x %4d", dm.dmPelsWidth, dm.dmPelsHeight);
          int x = SendMessage(ctrl, CB_FINDSTRINGEXACT, -1, (LPARAM)buf);
          if (x == CB_ERR)    // don't add duplicates
            SendMessage(ctrl, CB_ADDSTRING, 0, (LPARAM)buf);
        }
      }
      
      // try to find selection matching current settings
      sprintf(buf, "  %4d x %4d", s_instance->m_winDimensions.x, s_instance->m_winDimensions.y);
      int x = SendMessage(ctrl, CB_FINDSTRING, -1, (LPARAM)buf);
      if (x != CB_ERR)
        SendMessage(ctrl, CB_SETCURSEL, x, 0);
    }

    // AA COMBO BOX:
    {
      HWND ctrl = GetDlgItem(hwndDlg, IDC_AA);
      SendMessage(ctrl, CB_ADDSTRING, 0, (LPARAM)"  none");
      SendMessage(ctrl, CB_ADDSTRING, 0, (LPARAM)"  2x");
      SendMessage(ctrl, CB_ADDSTRING, 0, (LPARAM)"  4x");
      switch(s_instance->m_numSamples)
      {
        case 2:  SendMessage(ctrl, CB_SETCURSEL, 1, 0); break;
        case 4:  SendMessage(ctrl, CB_SETCURSEL, 2, 0); break;
        default: SendMessage(ctrl, CB_SETCURSEL, 0, 0); break;
      }
    }
  }
  else if (uMsg==WM_COMMAND)
  { 
    int id=LOWORD(wParam);
    if (id==IDOK || id==IDC_CANCEL)
    {
      if (id==IDOK)
      {
        // save config panel settings:
        s_instance->m_bUserOverride    = IsDlgButtonChecked(hwndDlg, IDC_USEDEFAULT) == 0;
        s_instance->m_bVSync           = IsDlgButtonChecked(hwndDlg, IDC_VSYNC) != 0;
        s_instance->m_bHelpButton      = IsDlgButtonChecked(hwndDlg, IDC_SHOWHELP) != 0;

        // read video mode combobox
        HWND ctrl = GetDlgItem(hwndDlg, IDC_DISPMODE);
        char buf[256];
        int sel = SendMessage(ctrl, CB_GETCURSEL, 0, 0);
        if (sel != CB_ERR)
        {
          SendMessage(ctrl, CB_GETLBTEXT, sel, (LPARAM)buf);
          char tmp[256];
          sscanf(buf, "%d %s %d", &s_instance->m_winDimensions.x, tmp, &s_instance->m_winDimensions.y);
        }

        // read AA combobox
        ctrl = GetDlgItem(hwndDlg, IDC_AA);
        sel = SendMessage(ctrl, CB_GETCURSEL, 0, 0);
        if (sel != CB_ERR)
        {
          switch(sel)
          {
          case 0: s_instance->m_numSamples = 1; break;
          case 1: s_instance->m_numSamples = 2; break;
          case 2: s_instance->m_numSamples = 4; break;
          }
        }

        s_instance->SaveConfig();
      }
      EndDialog(hwndDlg,id);
    }
    
  } 
  else if (uMsg==WM_DESTROY) {
    //blah...

  }

  // gray out all options if useDefault is set
  if (uMsg==WM_INITDIALOG || (uMsg==WM_COMMAND && LOWORD(wParam)==IDC_USEDEFAULT)) 
  {
    bool bChecked = IsDlgButtonChecked(hwndDlg, IDC_USEDEFAULT) != 0;
    EnableWindow( GetDlgItem(hwndDlg, IDC_CUSTOMGROUP), !bChecked );
    EnableWindow( GetDlgItem(hwndDlg, IDC_DISPMODELABEL), !bChecked );
    EnableWindow( GetDlgItem(hwndDlg, IDC_DISPMODE), !bChecked );
    EnableWindow( GetDlgItem(hwndDlg, IDC_AALABEL), !bChecked );
    EnableWindow( GetDlgItem(hwndDlg, IDC_AA), !bChecked );
    EnableWindow( GetDlgItem(hwndDlg, IDC_AFLABEL), !bChecked );
    EnableWindow( GetDlgItem(hwndDlg, IDC_AF), !bChecked );
    EnableWindow( GetDlgItem(hwndDlg, IDC_VSYNC), !bChecked );
    EnableWindow( GetDlgItem(hwndDlg, IDC_SHOWHELP), !bChecked );
  }

  return FALSE;
}

void TerrainDemo::SaveConfig()
{
  std::ofstream of;
  of.open("userOverrides.txt");

  // use defaults
  of << (m_bUserOverride?"false":"true") << std::endl;

  // resolution
  of << m_winDimensions.x << " " << m_winDimensions.y << std::endl;

  // aa
  of << m_numSamples << std::endl;

  // af
  //of << m_fLichenAniso << std::endl;

  // vsync
  of << (m_bVSync?"true":"false") << std::endl;

  // show help
  of << (m_bHelpButton?"true":"false") << std::endl;

  of.close();

}

void TerrainDemo::ClearChunk(int lod, int i, int j, int k) 
{
  td_chunkinfo* p = &m_chunks[lod][i][j][k];
  if (p->vbId >= 0) {
    m_free_vb_list[ m_num_free_vbs++ ] = p->vbId;
    assert(m_num_free_vbs <= m_num_vb);
  }
  p->vbId = -1;
  p->bHasPolys = TB_UNKNOWN;
  p->opacity = 0;
}


TerrainDemo::TerrainDemo(const char *lpCmdLine, HINSTANCE hInstance) : 
  m_menuSelector(NULL),
  m_sliderManager(NULL),
  m_sliderFile("sliders.txt"),
  m_bConfigMode(false),
  m_bUserOverride(false),
  m_bFreezeTess(false)
{
  assert(s_instance == NULL);
  s_instance = this;
  g_instance = this;
  
  m_bDontUpdateFpsNextFrame = false;

  m_prog_initialized = false;
  m_preset_initialized = false;

  m_bViewCull = true;

  m_nVoxelDim = 0;

  m_num_vb = NUM_VB;
  m_num_free_vbs = m_num_vb;
  for (int i=0; i<m_num_vb; i++) 
    m_free_vb_list[i] = i;
  for (int lod=0; lod<NUM_LOD; lod++) 
  {
    for (int i=0; i<CHUNKS_PER_DIM; i++)
      for (int j=0; j<CHUNKS_PER_DIM; j++)
        for (int k=0; k<CHUNKS_PER_DIM; k++) {
          m_chunks[lod][i][j][k].i = i; //THESE NEVER CHANGE
          m_chunks[lod][i][j][k].j = j; //THESE NEVER CHANGE
          m_chunks[lod][i][j][k].k = k; //THESE NEVER CHANGE
          m_chunks[lod][i][j][k].lod = lod; //THESE NEVER CHANGE
          m_chunks[lod][i][j][k].vbId = -1;
          ClearChunk(lod,i,j,k);
        }
  }
  m_wsChunkSize[0] = 4;  //TWEAK
  m_wsChunkSize[1] = 10; //TWEAK
  m_wsChunkSize[2] = 32; //TWEAK

  for (int i=0; i<NUM_VB; i++) {
    m_vb_list[i] = NULL;
    m_ib_list[i] = NULL;
  }

  // config panel settings:
  m_bWidescr = false;
  m_nFullscreenWidth = 0;
  m_nFullscreenHeight = 0;
  m_nAA = 1;//4;
  m_bVSync = true;//false;

  m_fHelpScrOpacity = 0;
  m_fAboutBoxOpacity = 0;

  m_bCursorShowing = true;
  m_bLastMouseKnown = false;

  m_bDrawingTiledScreenShot = false;

  m_timeScale = 1.0;
  m_nFrame = 0;
  m_nAnimFrame = 0;
  m_camForward = 0;

  m_bShowLogo = true;
  m_bHelpButton = true;
  m_nUberQuality = 0;

  m_time_since_prog_start = 0;
  m_time_since_last_user_activity = 0;
  //m_time_unpausable  = 0;
  m_time_pausable    = 0;
  m_last_mousemove_time = 0;
  m_last_mousedrag_time = 0;
  m_last_keydown_time = 0;
  m_non_regressable_anim_time = 0;    // this one should never regress (even on a reset/randomize) (for particles)
  m__prev_frame_raw_time = -1;
  m_dt_undamped      = 0.03333;
  m_dt_unpausable    = 0.03333;
  m_dt_pausable      = 0.03333;
  m_dt_anim          = 0.03333;
  m_fps              = 30.0f;
  
  // we are drawing the real deal
  m_wireframe = false; 

  m_showingCursor = true;
  //m_fovSliderMenu = NULL;  

  m_viewer      = NULL;
  m_primaryPass = NULL;

  m_bFlushOnNonSLI = false;

  m_pTopMenu = NULL;
  m_pSliderMenu = NULL;

  m_Paused = false;
  m_SlowMotion = false;

  m_ctrlIsDown  = false;
  m_shiftIsDown = false;

  // set up m_rand[] with some good random numbers:
  srand(timeGetTime());
  for (int i=0; i<sizeof(m_rand_seeds)/sizeof(m_rand_seeds[0]); i++) 
    m_rand_seeds[i] = (DWORD)rand();
  GenerateRandNumbersFromSeeds();

  // find the reg key that tells us where the data files were installed:
  char szInstallDir[512];
  strcpy(szInstallDir, ".\\");

  // if we could not open the file, fail
  //std::string argsString(lpCmdLine);
  //if (argsString.size() == 0)
  //  argsString = "args.txt";
  char args[] = "args.txt";

  NDataView configView(args);
  const char *data = (const char *) configView.GetData();
  if (data == NULL){
    char buf[1024];
    char szCurPath[MAX_PATH];
    ::GetCurrentDirectory(sizeof(szCurPath)-1, szCurPath);
    sprintf(buf, "could not open %s\n[current path: %s]\n[install dir: %s]", args, szCurPath, szInstallDir);
    ::MessageBox(NULL, buf, "error", MB_OK|MB_TASKMODAL|MB_SETFOREGROUND);
    //NOutputString("TerrainDemo: Data file ");
    //NOutputString(lpCmdLine);
    //NOutputString(" not valid\n");
    Quit();
  }
  
  // make a stream out of it and read in our arguments
  std::istrstream fstr(data, configView.GetSize());
  assert(fstr);

  std::string tmpToken;
  std::vector<std::string> nma_list;
  const std::string trueString("true");

  GetNextToken(fstr, tmpToken); 
  while (tmpToken == "consume") {
    GetNextToken(fstr, tmpToken); 
    nma_list.push_back(tmpToken);
    GetNextToken(fstr, tmpToken);  // another "consume"?
  };

  bool isFullscreen = (tmpToken == trueString);

  GetNextToken(fstr, tmpToken);  int fsx = atoi(tmpToken.c_str());
  GetNextToken(fstr, tmpToken);  int fsy = atoi(tmpToken.c_str());
  GetNextToken(fstr, tmpToken);  int wx = atoi(tmpToken.c_str());  wx &= 0xFFF0;  // otherwise tiled screenshots can break
  GetNextToken(fstr, tmpToken);  int wy = atoi(tmpToken.c_str());
  m_winDimensions.x = (isFullscreen ? fsx : wx);
  m_winDimensions.y = (isFullscreen ? fsy : wy);

  GetNextToken(fstr, tmpToken);
  m_pixelCorrection = float( atof(tmpToken.c_str()) );

  GetNextToken(fstr, tmpToken);
  uint cdepth = atoi(tmpToken.c_str());

  GetNextToken(fstr, tmpToken);
  uint zdepth = atoi(tmpToken.c_str());

  GetNextToken(fstr, tmpToken);
  uint sdepth = atoi(tmpToken.c_str());

  GetNextToken(fstr, tmpToken);
  bool bFindBestFullscreenDisplayMode = (tmpToken == trueString);

  GetNextToken(fstr, tmpToken);
  m_bVSync = (tmpToken == trueString);

  //GetNextToken(fstr, tmpToken);
  //m_bFlushOnNonSLI = (tmpToken == trueString);

  //GetNextToken(fstr, tmpToken);
  GetNextToken(fstr, tmpToken);
  m_numSamples = atoi(tmpToken.c_str());
  m_numSamples = min(16, max(1, m_numSamples));
  
  GetNextToken(fstr, tmpToken);
  m_fSuperSampling = (float)atof(tmpToken.c_str());
  m_fSuperSampling = max(0.5f, min(2.0f, m_fSuperSampling));

  GetNextToken(fstr, tmpToken);
  m_nUberQuality = atoi(tmpToken.c_str());

  GetNextToken(fstr, tmpToken);
  m_bShowLogo = (tmpToken == trueString);

  GetNextToken(fstr, tmpToken);
  m_bHelpButton = (tmpToken == trueString);

  GetNextToken(fstr, tmpToken);
  m_bCaptureFrames = (tmpToken == trueString);

  GetNextToken(fstr, tmpToken);
  m_nCaptureTiling = atoi(tmpToken.c_str());

  GetNextToken(fstr, tmpToken);
  bool usingHAL = (tmpToken == trueString);


  // <ADD PARAMS HERE...>
  // <ADD PARAMS HERE...>
  // <ADD PARAMS HERE...>
  GetNextToken(fstr, tmpToken);
  int N = atoi(tmpToken.c_str());
  //N = (N/16) * 16;
  m_nVoxelDim = N;

  GetNextToken(fstr, tmpToken);
  int M = atoi(tmpToken.c_str());
  m_nMargin   = M;

  m_nVoxelDimPlusMargins = m_nVoxelDim + m_nMargin*2;

  //m_fChunkLog = 2.0f;//1.65f;
  //m_nVoxelDim[2] = (int)((float)N/powf(m_fChunkLog,0)) & 0xFFF8;
  //m_nVoxelDim[1] = (int)((float)N/powf(m_fChunkLog,1)) & 0xFFF8;
  //m_nVoxelDim[0] = (int)((float)N/powf(m_fChunkLog,2)) & 0xFFF8;




  //---------------- DONE READING IN ARGS.TXT ---------------------
  //---------------- DONE READING IN ARGS.TXT ---------------------
  //---------------- DONE READING IN ARGS.TXT ---------------------
  GetNextToken(fstr, tmpToken);
  // if this fails, you screwed something up in args.txt!!
  assert(!strcmpi(tmpToken.c_str(), "cheese"));








  // if win_height was 0, auto-determine best display mode:
  // (this will modify win_width, ren_width, etc.)
  if (isFullscreen) {
    // auto-determine with args.txt rez as target
    if (bFindBestFullscreenDisplayMode) {
      FindBestFullscreenDisplayMode(&m_winDimensions.x, &m_winDimensions.y, &m_renDimensions.x, &m_renDimensions.y, cdepth);
    }
    else {
      m_renDimensions.x = m_winDimensions.x;
      m_renDimensions.y = m_winDimensions.y;
    }
  }
  else {
    // windowed
    m_renDimensions.x = m_winDimensions.x;
    m_renDimensions.y = m_winDimensions.y;
  }

  // how much do we have to squish the final quad to get a 16:9 output?
  m_quadScaleFactor = m_pixelCorrection * (float)m_renDimensions.y / (float)m_winDimensions.y;

  // check for a command line arg
  if (lpCmdLine[0]!=0)
  {
    // allow both /c and -c to enable the config panel
    if (lpCmdLine[0] == '/' || lpCmdLine[0] == '-')
    {
      char _switch = lpCmdLine[1];
      _switch = TO_UPPER(_switch);

      if (_switch=='C')
      {
        m_bConfigMode = true;
      }
    }
  }

  LoadConfig(); // these are the userOverride settings

  if (m_bConfigMode)
  {
    // launch a modal config dialog box
    DialogBox(hInstance, MAKEINTRESOURCE(DLG_SCRNSAVECONFIGURE), NULL, ConfigProc);
    Quit();
  }



  //
  // build the NRWindow and give it event handlers
  //
  NRWindow *window = NRWindow::CreateNRWindow(m_winDimensions, cdepth, zdepth, sdepth, m_numSamples, isFullscreen, isFullscreen ? m_bVSync : false, true);
  window->SetKeyDownCB      (KeyDownCB);
  window->SetKeyUpCB        (KeyUpCB);
  window->SetMouseCB        (MouseButtonCB);
  window->SetMotionCB       (MouseDragCB);
  window->SetPassiveMotionCB(MouseMoveCB);
  window->SetResizeCB       (ResizeCB);
  window->SetDisplayCB      (UpdateCB);
  ((NRWindowDX *)(window))->setUsingRefrast(!usingHAL);

  // build a viewer around the window
  m_viewer = new NVViewer(window->GetRenderer());

  // rename the window
  HWND hWnd = m_viewer->GetRenderer()->GetWindow()->GetWindowHandle();
  assert(hWnd);
  ::SetWindowText(hWnd, WINDOW_TITLE);

//  NSplashScreen splashScreen(hInstance, 584, 320);
//  splashScreen.Show("Loading...");
//  splashScreen.SetFullScreen(true);

  // read in the first scene 
  NVScene *scene = NULL;
  NVFileReader fileReader;
  if (!fileReader.ReadModel(nma_list[0], scene)){
    NOutputString("TerrainDemo: Bad Data file: " + nma_list[0] + "\n");
    Quit();
  }  

  // read in & consume all the other scenes
  for (uint i=1; i<nma_list.size(); i++) {
    NVScene *s = NULL;
    if (!fileReader.ReadModel(nma_list[i], s)) {
      NOutputString("TerrainDemo: Bad Data file: " + nma_list[i] + "\n");
      Quit();
    }
    scene->Consume(s);
    delete s;
  }
  
  //m_animManager = new NVPlaybackControl(m_viewer, scene);  
  NVStaticMenu *menu = dynamic_cast<NVStaticMenu*>(NVNAMESPACE->GetByName("oo_main_menu"));
  if (menu != NULL) {
	  //m_animManager->m_topLevelMenu->AppendMenuItems(menu);
  }

  ResolveReferencesRecur(scene);
  InitDemo( scene );

  #ifdef WRITE_MERGED_FILE
    std::ofstream mStr("merged.nma", std::ios::out | std::ios::binary);
    assert(mStr);

    NVWriteContext writeContext(mStr, false);
    scene->Write(&writeContext);
  #endif // 1

  // Hard-coded main pass
  m_primaryPass = (NVRenderPass*) NVNAMESPACE->GetByName("mainDrawPass2");
  assert(m_primaryPass);
    // clear the screen in composite_f if necessary:
    //m_primaryPass->SetClearsColor( m_quadScaleFactor < 0.99f );

  // Hard-coded main camera
  NVBaseCamera *userCamera = (NVBaseCamera*) NVNAMESPACE->GetByName("mainCamShape");
  assert(ISA(userCamera, NVBaseCamera*));
  m_viewer->SetUserCamera(userCamera);

  // have a maya navigator at the ready
  NVTransform *cameraTransform = m_primaryPass->GetCamera()->GetTransform();
  
  // start as a spherical navigator
  m_sphericalNavigator = new NVSphericalNavigator(m_viewer, cameraTransform, true);
  m_sphericalNavigator->SetResponse(NVec3f(-1.0f, -0.9f, 200.0f));
  m_sphericalNavigator->Constrain  (-HALF_PI_F, HALF_PI_F, 2.1f, 100.0f);
  m_sphericalNavigator->SetDistance(DEFAULT_CAMERA_DISTANCE);
  m_sphericalNavigator->SetLookAt  (NPnt3f(0, 0, 0));

  // Set our navigator to be the spherical navigator for now
  m_viewer->SetNavigator(m_sphericalNavigator);
  m_navigator = m_sphericalNavigator;
}

TerrainDemo::~TerrainDemo(){

  // get the window
  if (m_viewer) {
    NRWindow *nwindow = m_viewer->GetRenderer()->GetWindow();

    // delete the viewer that we build around it
    delete m_viewer;

    // delete the window
    delete nwindow;
  }

  // clean up audio loops
  int N = m_sounds.size();
  for (int i=0; i<N; i++) {
    SAFE_DELETE(m_sounds[i]);
  }
  m_sounds.clear();
  DemoSoundFmod::CloseSoundSystem();

  // there are no longer any active
  s_instance = NULL;
}

void TerrainDemo::MouseButton(int button, int state, int x, int y)
{
  if (m_nFrame==0)
    return;

  float fx = x/(float)m_winDimensions.x;
  float fy = y/(float)m_winDimensions.y;

  // did user click our hacky 'click here for help' image?
  if (m_bHelpButton && 
      !m_pSliderMenu->IsVisible() &&
      !m_sliderManager->GetSliderMenu()->IsVisible()) 
  {
    NVShape *pressF1_s = dynamic_cast<NVShape*>(NVNAMESPACE->GetByName("clickhelp_shape")); assert(pressF1_s);
    NVFloatParam *param = dynamic_cast<NVFloatParam*>(pressF1_s->GetVertexParam("scale")); assert(param);
    NVFloatParam *offsetParam = dynamic_cast<NVFloatParam*>(pressF1_s->GetVertexParam("offset")); assert(offsetParam);
    float button_x = ( (fx*2-1) - offsetParam->GetValue().x) / param->GetValue().x;
    float button_y = (-(fy*2-1) - offsetParam->GetValue().y) / param->GetValue().y;
    if (button_x>-1 && button_x<1 && button_y>-1 && button_y<1) {
      // show help screen
      if (state==0) {
        m_fHelpScrOpacity = (m_fHelpScrOpacity > 0.3f) ? 0 : SECONDS_TO_SHOW_HELP_SCREEN;
        m_fAboutBoxOpacity = 0;
      }
      return;
    }
  }

  // did user click help or about screens?
  if (m_fHelpScrOpacity > 0) {
    NV2DTexture* tex = dynamic_cast<NV2DTexture*>(NVNAMESPACE->GetByName("helpscrTex")); assert(tex);
    NVec3i dim = tex->GetTexture()->GetDimensions();
    if (fx*m_winDimensions.x > m_winDimensions.x/2 - dim.x/2 && 
        fx*m_winDimensions.x < m_winDimensions.x/2 + dim.x/2 && 
        fy*m_winDimensions.y > m_winDimensions.y/2 - dim.y/2 && 
        fy*m_winDimensions.y < m_winDimensions.y/2 + dim.y/2)
    {
      if (state==0) {
        m_fHelpScrOpacity = 0;      
        return;
      }
    }  
    //return;
  }
  if (m_fAboutBoxOpacity > 0) {
    NV2DTexture* tex = dynamic_cast<NV2DTexture*>(NVNAMESPACE->GetByName("aboutboxTex")); assert(tex);
    NVec3i dim = tex->GetTexture()->GetDimensions();
    if (fx*m_winDimensions.x > m_winDimensions.x/2 - dim.x/2 && 
        fx*m_winDimensions.x < m_winDimensions.x/2 + dim.x/2 && 
        fy*m_winDimensions.y > m_winDimensions.y/2 - dim.y/2 && 
        fy*m_winDimensions.y < m_winDimensions.y/2 + dim.y/2)
    {
      if (state==0) {
        m_fAboutBoxOpacity = 0;      
        return;
      }
    }  
    //return;
  }

  ResetUserActivityTimer();

  if (m_viewer != NULL)
    m_viewer->OnMouseButton(button, state, x, y);
}

void TerrainDemo::MouseDrag(int x, int y){
  if (m_nFrame==0)
    return;
  if (m_viewer != NULL)
    m_viewer->OnMouseMove(x, y);

  bool bMouseMoved = (m_bLastMouseKnown && (x!=m_nLastMouseX || y!=m_nLastMouseY));
  m_nLastMouseX = x;
  m_nLastMouseY = y;
  m_bLastMouseKnown = true;

  if (bMouseMoved) {
    //EnableAutoPilot(false, true);// disable AutoPilot if the user is dragging
    ResetUserActivityTimer();
    m_last_mousemove_time = m_time_since_prog_start;
    m_last_mousedrag_time = m_time_since_prog_start;
  }
}

void TerrainDemo::MouseMove(int x, int y){
  if (m_nFrame==0)
    return;

  // adjust the position of the mouse so it works with non-square pixels (correction specified in args.txt)
  {
    float centerY = (float)m_winDimensions.y / 2.0f;
    float distanceFromCenter = (float)y - centerY;
    float correctedDistanceFromCenter = distanceFromCenter / m_pixelCorrection;
    float correctedY = centerY + correctedDistanceFromCenter;
    y = (int)correctedY;
  }

  if (m_viewer != NULL)
    m_viewer->OnMouseMove(x, y);
  ResetUserActivityTimer();//rg

  bool bMouseMoved = (m_bLastMouseKnown && (x!=m_nLastMouseX || y!=m_nLastMouseY));
  m_nLastMouseX = x;
  m_nLastMouseY = y;
  m_bLastMouseKnown = true;

  if (bMouseMoved) {
    m_last_mousemove_time = m_time_since_prog_start;
  }
}

void TerrainDemo::Resize(int w, int h) {
  if (m_viewer != NULL)
    m_viewer->OnResize(w, h);

  m_winDimensions.x = w;
  m_winDimensions.y = h;

  //UpdateLogoSizePosition();
}

void TerrainDemo::KeyUp(uchar key, int x, int y) {

  // cache control and shift key up/down status
  if (key == NR_KEY_SHIFT)
    m_shiftIsDown = false;
  if (key == NR_KEY_CONTROL){
    m_ctrlIsDown = false;
  }

  // on to the viewer
  if (m_viewer != NULL)
    m_viewer->OnKeyUp(key, x, y);
}

void TerrainDemo::SetWireframe(bool wireframe)
{
  // toggle wire drawing on/off for all the desired passes here:
  std::string wf_passes[] = {
    "mainDrawPass2",
    //"otherDrawPass", 
    // ...
  };
  for (uint i=0; i<sizeof(wf_passes)/sizeof(wf_passes[0]); i++) {
    NVRenderPass *p = GetRenderPass(wf_passes[i]);
    if (p)
      p->SetWireframeForPass( m_viewer->GetScene(), wireframe );
  }

  // also make main pass clear when wireframe mode is on:
  //GetRenderPass("mainDrawPass")->SetClearsColor( wireframe ? true : (m_quadScaleFactor < 0.99f) );

  m_wireframe = wireframe;

  //NVOnOffButton *button = dynamic_cast<NVOnOffButton*>(NVNAMESPACE->GetByName("wireframe_button"));
  //if (button != NULL)
  //  button->SetState(wireframe);

}

void TerrainDemo::KeyDown(uchar key, int x, int y) {
  if( NULL == m_viewer ){
    assert( 0 );
    return;
  }
  NVScene *scene = m_viewer->GetScene();
  if( NULL == scene ){
    assert( 0 );
    return;
  }

  m_last_keydown_time = m_time_since_prog_start;
  ResetUserActivityTimer();

  // for mapping SHIFT+0-9 to 0-9...
  static bool bSymbolMapReady = false;
  static int symbol_to_num[256];
  if (!bSymbolMapReady) {
    symbol_to_num[')'] = 0;
    symbol_to_num['!'] = 1;
    symbol_to_num['@'] = 2;
    symbol_to_num['#'] = 3;
    symbol_to_num['$'] = 4;
    symbol_to_num['%'] = 5;
    symbol_to_num['^'] = 6;
    symbol_to_num['&'] = 7;
    symbol_to_num['*'] = 8;
    symbol_to_num['('] = 9;
    bSymbolMapReady = true;
  }

  //
  // Other keys do a variety of other things...
  //
  NVStockObjectCatalog *catalog = NVCATALOG;

  char buf[256];

  // cache some key state but do little about it:
  if (key == NR_KEY_SHIFT) {
    m_shiftIsDown = true;
    return;
  }
  if (key == NR_KEY_CONTROL) {
    m_ctrlIsDown = true; 
    return;
  }

  if (!m_ctrlIsDown) {
    switch(key){

    case 'H':
    case 'h':
    case '?':
    case NR_KEY_F1:
      m_fHelpScrOpacity = (m_fHelpScrOpacity > 0.3f) ? 0 : SECONDS_TO_SHOW_HELP_SCREEN;
      m_fAboutBoxOpacity = 0;
      break;

    //case 'a':
    //  m_fAboutBoxOpacity = (m_fAboutBoxOpacity > 0.3f) ? 0 : SECONDS_TO_SHOW_ABOUT_BOX;
    //  m_fHelpScrOpacity = 0;
    //  break;

    case (NR_KEY_ESCAPE):
      if (m_fHelpScrOpacity > 0)
        m_fHelpScrOpacity = 0;
      else if (m_fAboutBoxOpacity > 0)
        m_fAboutBoxOpacity = 0;
      else
        Quit();  
      break;

    case 'i': g_fDebugVal.x -= 0.1f; goto applyDebugInfo;
    case 'I': g_fDebugVal.x += 0.1f; goto applyDebugInfo;
    case 'j': g_fDebugVal.y -= 0.1f; goto applyDebugInfo;
    case 'J': g_fDebugVal.y += 0.1f; goto applyDebugInfo;
    case 'k': g_fDebugVal.z -= 0.1f; goto applyDebugInfo;
    case 'K': g_fDebugVal.z += 0.1f; goto applyDebugInfo;
    applyDebugInfo:
      {
        NVConstantBuffer* cb = dynamic_cast<NVConstantBuffer*>(NVNAMESPACE->GetByName("DebugInfoCB")); 
        assert(cb);
        NVFloatParam* fp = dynamic_cast<NVFloatParam*>(cb->FindParam("debugInfo")); 
        assert(fp);
        fp->SetValue( NPnt4f(g_fDebugVal.x, g_fDebugVal.y, g_fDebugVal.z, g_fDebugVal.w) );

        sprintf(buf, "debug = %f %f %f %f\n", g_fDebugVal.x, g_fDebugVal.y, g_fDebugVal.z, g_fDebugVal.w); 
        NOutputString(buf);
      }
      break;

    case 'S':
      // auto sliders
      {
        NVStaticMenu *sliderMenu = m_sliderManager->GetSliderMenu();
        if (sliderMenu->IsVisible())
          sliderMenu->SetVisible(false);
        else 
          sliderMenu->SetVisible(true);
      }
      break;
    
    case 's':
      // custom sliders
      m_pSliderMenu->SetVisible( !m_pSliderMenu->IsVisible() );
      break;

    case NR_KEY_RIGHT: m_menuSelector->Next(); break;
    case NR_KEY_LEFT:  m_menuSelector->Prev(); break;

    case ']': // save slider values.
      m_sliderManager->SaveValues(m_sliderFile);
      break;
    case '[': // load slider values.
      // in case you hit load instead of save by accident, you're not screwed
      m_sliderManager->SaveValues( "pre_load_sliders_backup.txt" );
      m_sliderManager->LoadValues(m_sliderFile);
      break;

    case 'c':
      m_bViewCull = !m_bViewCull;
      break;

    // RESET: resets everything but the roam lock (Panic button)
    case 'p': 
      {
        m_bFreezeTess = false;
        m_camForward = 0;
        m_pSliderMenu->SetVisible( false );
        m_sliderManager->GetSliderMenu()->SetVisible( false );
        m_sphericalNavigator->SetLookAt(NPnt3f(0,0,0));
        m_sphericalNavigator->SetDistance(DEFAULT_CAMERA_DISTANCE);
        m_sphericalNavigator->SetThrow(0, 0);
         NVBaseCamera* cam = m_primaryPass->GetCamera();
         NPnt3f camPos = cam->GetGlobalPosition();
        m_sphericalNavigator->SetYaw( atan2f(camPos.x,camPos.z) );
        m_sphericalNavigator->SetPitch(0);
        //((NVPerspectiveCamera*)(m_primaryPass->GetCamera()))->SetFieldOfView(m_fOriginalFOV);
        if (m_wireframe)
          SetWireframe(false);
        m_fHelpScrOpacity = 0;
        m_fAboutBoxOpacity = 0;
        m_SlowMotion = false;
        if (m_Paused)
          Pause(!m_Paused);

        // apply the default slider values:
        for (int i=0; i<g_nSliders; i++) {
          NVFloatParam* param = dynamic_cast<NVFloatParam*>(g_SliderCB->GetConstants()[i]);
          assert(param);
          g_nvsliders[i]->SetValueAndUpdate( (param->GetValue().w-param->GetMin().x)/(param->GetMax().x-param->GetMin().x)     );//param->GetValue().x );
        }
      }
      break;

    case NR_KEY_F4:
      m_viewer->GetScene()->ReloadTextures( m_viewer->GetRenderer() );
      break;

	  case NR_KEY_F8:
	  {
      // reload all shaders listed in "bin\shader_reload.txt"
		  FILE* f = fopen("shader_reload.txt", "r");
      if (f) {
  		  std::vector<std::string> shaders_to_reload;
        char buf[256];
        while (!feof(f)) {
          buf[0] = 0;
          if (fscanf(f, "%s", buf)>0 && buf[0]) {
            // handle comments:
            if (buf[0]=='/' && buf[1]=='/') {
              char ch;
              do { 
                ch = fgetc(f);
              } 
              while (ch != '\n' && !feof(f));
            }
            else
              shaders_to_reload.push_back(buf);
          }
          else
            break;
        }
        fclose(f);
		    ReloadSomeShaders(m_viewer->GetScene(), shaders_to_reload);
      }
	  }
	  break;

    case '+':
      m_camForward += 2.0f;
      break;
    case '-':
      m_camForward -= 2.0f;
      break;

    case NR_KEY_F9:
      ReloadAllShaders(m_viewer->GetScene());
      break;

  #if 0 // these reload shaders buttons should be disabled for final build
    case NR_KEY_F7: 
      {
        // reload only the rock gen shaders
        std::vector<std::string> shaders_to_reload;
        shaders_to_reload.push_back("build_field_f");
        ReloadSomeShaders(m_viewer->GetScene(), shaders_to_reload);
        break;
      }
  #endif

    case '~': // tilde = TILED screenshot (huge)
      if (m_nCaptureTiling > 0) {
        ScreenShot(m_nCaptureTiling);
      }
      break;

    case '`': // backquote 
      if (m_nCaptureTiling > 0)
      {
        // save screenshot. but only if you weren't already capturing all frames.
        if (!m_bCaptureFrames)
        {
          NOutputString("Saving current frame to file.\n");
          saveCurrentFrameToNumberedFile();
        }
        break;
      }

      /*
    case NR_KEY_F10:
      if ( m_globalShaderParamsMenu ) 
        {
          if (m_globalShaderParamsMenu->IsVisible())
            m_globalShaderParamsMenu->SetVisible(false);
          else  {
            m_globalShaderParamsMenu->SetVisible(true);
          }
        }
      break;
      */

      /*
    // re-anchor camera right in front of our nose:
    case NR_KEY_SPACE:
      {
        NVBaseCamera* cam = m_primaryPass->GetCamera();
        NPnt3f camPos = cam->GetGlobalPosition();
        NPnt3f lookAt = m_sphericalNavigator->GetLookAt();
        float cam_rad_xz = sqrtf(camPos.x*camPos.x + camPos.z*camPos.z);
        if (cam_rad_xz < 1.1f) 
        {
          float distance1 = m_sphericalNavigator->GetDistance();
          float distance2 = 0.05f;
          NPnt3f new_lookAt;
          new_lookAt.x = lookAt.x + (distance1 - distance2)*cam->GetGlobalDirection().x;
          new_lookAt.y = lookAt.y + (distance1 - distance2)*cam->GetGlobalDirection().y;
          new_lookAt.z = lookAt.z + (distance1 - distance2)*cam->GetGlobalDirection().z;

          m_sphericalNavigator->SetLookAt(new_lookAt);
          m_sphericalNavigator->SetDistance(distance2);
        }
      }
      break;
      */

    case 'F':
    case 'f':
      if (m_Paused)
      {
        // pause -> slomo
        Pause(!m_Paused);
        m_SlowMotion = true;
      }
      else if (m_SlowMotion)
      {
        // slomo -> regular
        m_SlowMotion = false;
      }
      else 
      {
        // regular -> pause
        Pause(!m_Paused);
      }
      break;

    case 'r':
      Randomize();
      // intentional fallthrough here!
    case 'z':
      for (int lod=0; lod<NUM_LOD; lod++) 
        for (int i=0; i<CHUNKS_PER_DIM; i++)
          for (int j=0; j<CHUNKS_PER_DIM; j++)
            for (int k=0; k<CHUNKS_PER_DIM; k++)
              ClearChunk(lod,i,j,k);
      break;

    case NR_KEY_SPACE:
      m_bFreezeTess = !m_bFreezeTess;
      break;

    case 'w':
    case 'W':
        SetWireframe(!m_wireframe);
        break;

      // otherwise give it to the viewer
    default:
      //m_viewer->OnKeyDown(key, x, y);
      break;
    }
  }
  else {
    // CTRL was held down...
  }
}




void TerrainDemo::ResetUserActivityTimer()
{
  // this is the timer that triggers AutoPilot mode after a period of inactivity
  m_time_since_last_user_activity = 0;
}




void TerrainDemo::UpdateCamera()
{
  NVBaseCamera* cam = m_primaryPass->GetCamera();
  NPnt3f camPos = cam->GetGlobalPosition();
  NPnt3f lookAt = m_sphericalNavigator->GetLookAt();
  NVec3f camDir = -cam->GetGlobalDirection();

  // <manually influence the camera here if you want>
  if (m_nFrame > 4) {
    NVec3f moveCamDir = camDir;
    if (moveCamDir.y < -0.8f)
      moveCamDir.y += 0.8f;
    else if (moveCamDir.y > 0.4f)
      moveCamDir.y -= 0.4f;
    else
      moveCamDir.y = 0;
    moveCamDir = normalize(moveCamDir);
    lookAt.x += moveCamDir.x*m_camForward*(float)m_dt_pausable;
    lookAt.y += moveCamDir.y*m_camForward*(float)m_dt_pausable;
    lookAt.z += moveCamDir.z*m_camForward*(float)m_dt_pausable;
    m_sphericalNavigator->SetLookAt(lookAt, true);
  }

  // tell shaders (via constant buffers) where the camera & lookat points are
  NVConstantBuffer* cb = dynamic_cast<NVConstantBuffer*>(NVNAMESPACE->GetByName("g_GlobalRockCB"));
  assert(cb);
  ((NVFloatParam*)(cb->FindParam("wsEyePos")))->SetValue( NPnt4f(camPos.x, camPos.y, camPos.z, 0));
  ((NVFloatParam*)(cb->FindParam("wsLookAt")))->SetValue( NPnt4f(lookAt.x, lookAt.y, lookAt.z, 0));
}

void TerrainDemo::UpdateClickHelpAndLogo()
{
  // updates 'press F1' message AND also the NV logo...

  float time_since_user_interaction = (float)(m_time_since_prog_start - max(m_last_mousemove_time, m_last_keydown_time));
  float opacity = max(0, BURNIN_DELAY - time_since_user_interaction);
    // turn off sliders after 60 seconds: [since they draw slowly!]
    if (time_since_user_interaction > 60.0f)
      m_pSliderMenu->SetVisible( false );

  // 1. update logo opacity.  Turn it off after 10 min. of non-interactive mode.
  {
    NVShape *s = dynamic_cast<NVShape*>(NVNAMESPACE->GetByName("nvLogo_quadProxy_shape"));
    assert(s);
    NVFloatParam *cp = dynamic_cast<NVFloatParam*>(s->GetFragmentParam("opacity"));
    assert(cp);
    cp->SetValue(NPnt4f(opacity,0,0,0));
  }

  // 2. update PRESS F1
  NVShape *s = dynamic_cast<NVShape*>(NVNAMESPACE->GetByName("clickhelp_shape"));
  if(!s)
      return;
  NVFloatParam *p = dynamic_cast<NVFloatParam*>(s->GetFragmentParam("opacity"));
  if(!p)
      return;

  if (!m_bHelpButton)
    opacity = 0;
  
  if (opacity > 0) {
    s->SetCull(NV_CULL_NEVER);
    p->SetValue(NPnt4f(opacity,0,0,0));
  }
  else {
    s->SetCull(NV_CULL_ALWAYS);
  }
}



void TerrainDemo::UpdateTime(bool paused, bool slowmo, double animTimeScale, bool bRandomize)
{
  if (m_bCaptureFrames)
  {
    // We're capturing all frames to disk, presumably to make a video, so freeze the framerate.
    // Override time globally, so even the Navigator's throws go the right speed
    NSetTimeOverride(NGetTime() + 1.0/30.0);
  }

  double cur_raw_time = NGetTime();

	if (m_nFrame==0 || bRandomize) {
    double r = (double)(rand() % 12345);
    //m_cam_time        = r;
    //m_misc_time       = r;
    if (m_nFrame==0) {  
      m_time_since_prog_start = 0;
		  //m_time_unpausable = 0;
      m_time_pausable   = 0;
      m_non_regressable_anim_time = r; // only reset this **ONCE** at startup!
    }
    m__prev_frame_raw_time = cur_raw_time - 0.03333333;
	}

  double sloMoMult = (slowmo ? (1.0/(double)(SLOW_MOTION_SPEED_DIV)) : 1);

  double new_dt = max(0.001f, min(0.5f, (cur_raw_time - m__prev_frame_raw_time) ));

  m_dt_undamped = new_dt;
  // damping: [smooth out crappy system clocks!]
  if (m_nFrame > 4) {
    if (new_dt > 0.5f)
      new_dt = m_dt_unpausable;
    else
      new_dt = new_dt*0.1f + 0.9f*m_dt_unpausable;
  }
  // NOTE: this is showing huge jumps [bad timer precision] once a second,
  // BUT if you set dt to a constant, animation *looks* 100% smooth!
  // so it must be numerical error in NGetTime...?!?!
  // Note that running the dt detection code in WinMain comes up with pristine results
  // (consistent 0.05 *ms* steps)... so DX must somehow be interfering with the system timer?
  //char buf[64];
  //sprintf(buf, "%.3f\n", cur_raw_time);
  //NOutputString(buf);
  
  m_dt_unpausable = new_dt;//0.028f;//m_dt_unpausable*0.85 + 0.15*new_dt;
  if (m_dt_unpausable > 1.0)   // filter out debugger pauses, etc.
    m_dt_unpausable = 0.033333;
  m_dt_pausable = paused ? 0/*(m_preset_initialized ? 0 : 0.033)*/ : m_dt_unpausable*sloMoMult;
  m_dt_anim     = m_dt_pausable*animTimeScale;

  if (m_nFrame < 4)
    m_fps = (float)(1.0f/m_dt_undamped);
  else if (!m_bDontUpdateFpsNextFrame)
    m_fps = (float)(m_fps*0.95f + 0.05f/m_dt_undamped);
  m_bDontUpdateFpsNextFrame = false;

  m_time_since_prog_start     += m_dt_unpausable;
  m_time_since_last_user_activity += m_dt_pausable;
  //m_time_unpausable         += m_dt_unpausable;
  m_time_pausable             += m_dt_pausable;
  //m_misc_time               += m_dt_anim;
  //m_cam_time                += m_dt_anim;

  m_non_regressable_anim_time += m_dt_anim;

  m__prev_frame_raw_time = cur_raw_time;
}


void TerrainDemo::GenerateRandNumbersFromSeeds()
{
  const int nNumbersPerSeed = 157;
  int nSeeds = sizeof(m_rand_seeds)/sizeof(m_rand_seeds[0]);
  int seed = 0;
  #define RANDBANK_SIZE 17 
  DWORD randbank[RANDBANK_SIZE];
  ZeroMemory(randbank, sizeof(randbank));
  for (int i=0; i<sizeof(m_rand)/sizeof(m_rand[0]); i++) {
    if ((i % nNumbersPerSeed) == 0) {      // this keeps [PSEUDOrandom] random number generation from getting in a rut
      //srand(timeGetTime() + (i/57)*137);
      srand( m_rand_seeds[seed] & RAND_MAX );
      seed = (seed + 1) % nSeeds;
    }
    DWORD r = rand();
    r ^= randbank[ rand() % RANDBANK_SIZE ];
    randbank[ rand() % RANDBANK_SIZE ] = rand();
    m_rand[i] = (double)(r) / (double)(RAND_MAX-1);
  }
  //m_prog_initialized = true;  --> SET THIS @ END OF FN
}


void TerrainDemo::saveCurrentFrameToNumberedFile()
{
  const unsigned int maxDigitsInCaptureFilename = 5;
  std::string filename = "capture";

  // make a filename
  {
    static int nShotCount = 0;
    char number[20];
    while (1)
    {
      itoa(nShotCount, number, 10);
      std::string testFilename = filename;
      for (uint i=maxDigitsInCaptureFilename; i>strnlen(number, 20); i--)
      {
        testFilename += "0"; // leading zeros so the files sort properly by name
      }
      testFilename += number;
      testFilename += ".png";

      if (GetFileAttributes(testFilename.c_str()) == INVALID_FILE_ATTRIBUTES)
      {
        filename = testFilename;
        break;
      }
      else 
      {
        nShotCount++;
      }
    }
  }
  
  // ask the renderer to save off its framebuffer
  NRenderDX *renderer = dynamic_cast<NRenderDX*>(m_viewer->GetRenderer());
  assert(renderer);
  assert(ISA(renderer, NRenderDX*));
  renderer->CaptureFrameToFile(filename);
}

void TerrainDemo::Update()
{
  if ( //m_viewer->GetRenderer()->IsDeviceLost() ||
       ::GetFocus() != m_viewer->GetRenderer()->GetWindow()->GetWindowHandle() 
      ) 
  {
    Sleep(200);
    return;  // so our sizeblobs stuff doesn't try to create any VB's, and so our perf testing doesn't measure any frames
  }

  // update times & dt's
  UpdateTime(m_Paused, m_SlowMotion, m_timeScale, !m_prog_initialized);
  UpdateCamera();
  UpdateHelpScreen();
  UpdateAboutBox();
  if (m_bShowLogo)
    UpdateClickHelpAndLogo();

  // <update your own stuff here>
  // ...
  
  // update sounds, lights, animations; cpu-process meshes, bump the transform state, etc..
  m_viewer->Update1( m_time_pausable );
  
  // update the viewer (runs all renderpasses + redraws the screen)
  m_viewer->Update2( m_time_pausable );
  
  // save screenshots
  if (m_bCaptureFrames) {
    saveCurrentFrameToNumberedFile();
  }

  // re-dirty the scree
  m_viewer->GetRenderer()->GetWindow()->PostRedisplay();

  // hide mouse cursor if it's been a while since a mousemove
  float fMouseShowDur = (float)(max(MOUSE_CURSOR_HIDE_TIME, m_dt_unpausable*3.0f));
  bool bCursorShouldBeShowing = m_time_since_prog_start < m_last_mousemove_time + fMouseShowDur;
  if (m_bCursorShowing != bCursorShouldBeShowing) {
    ShowCursor(bCursorShouldBeShowing);
    m_bCursorShowing = bCursorShouldBeShowing;
  }

  if (m_nFrame < 4) {
    HWND hWnd = m_viewer->GetRenderer()->GetWindow()->GetWindowHandle();
    SetForegroundWindow(hWnd);
    SetActiveWindow(hWnd);
    SetFocus(hWnd);
  }

  m_nFrame++;   // this is NEVER reset
  m_nAnimFrame++;  // this can be reset on a preset load
  m_prog_initialized = true;
  
}

int RoundUp(float x) {
  if (x > 0)
    return (int)ceilf(x);
  else if (x < 0)
    return -((int)floorf(-x));
  return 0;
}
int SmartMod(int x, int y) {
  if (x < 0) {
    int n = -x/y;
    x += y*(n+3);
  }
  return x % y;
}
int SmartDiv(int x, int y) {
  // say y==8.  then;
  // x in:        -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10
  // returns: ... -2 -1 -1 -1 -1 -1 -1 -1 -1 0 0 0 0 0 0 0 0 1 1 1  ...
  if (x < 0)
    return -((-x + y-1)/y);
  return x / y;
}

int TerrainDemo::BuildLodChunkNOW(NVec3f wsChunkPos, int lod, int vbId)
{
  // where is this chunk in world space?
  NVFloatParam* fp = dynamic_cast<NVFloatParam*>(m_ChunkCB->FindParam("wsChunkPos"));
  assert(fp);
  fp->SetValue(NPnt4f(wsChunkPos.x, wsChunkPos.y, wsChunkPos.z, 0));
  m_ChunkCB->Update(*m_viewer->GetViewerInfo(), NULL);

  // wire all the LodCB's to point to the data for this LOD:
  int N = m_lod_CB_refs.size();
  for (int i=0; i<N; i++)
    m_lod_CB_refs[i]->SetReferencedNode( m_lod_CBs[lod] );


  // RUN THE PASSES.

  m_viewer->Render(m_build_density_pass, false); 

  ((NRenderDX*)(m_viewer->GetRenderer()))->BeginStreamOutQuery("mc_nonempty_cell_count");
  m_viewer->Render(m_list_nonempty_cells_pass, false);
  int nCells = ((NRenderDX*)(m_viewer->GetRenderer()))->EndStreamOutQuery("mc_nonempty_cell_count");

  if (nCells > 0) {
    // run the last 4 passes
      if (m_nFrame < 16) { ((NRenderDX*)(m_viewer->GetRenderer()))->BeginStreamOutQuery("x"); }
    m_viewer->Render(m_list_verts_to_generate_pass, false);
      if (m_nFrame < 16) { int nOut = ((NRenderDX*)(m_viewer->GetRenderer()))->EndStreamOutQuery("x"); char buf[64]; sprintf(buf, "m_list_verts_to_generate_pass: output=%d\n", nOut); NOutputString(buf); }
      if (m_nFrame < 16) { ((NRenderDX*)(m_viewer->GetRenderer()))->BeginStreamOutQuery("x"); }
    m_viewer->Render(m_splat_vertex_IDs_pass, false);
      if (m_nFrame < 16) { int nOut = ((NRenderDX*)(m_viewer->GetRenderer()))->EndStreamOutQuery("x"); char buf[64]; sprintf(buf, "m_splat_vertex_IDs_pass: output=%d\n", nOut); NOutputString(buf); }

    char buf[64];
    m_gen_vertices_pass->ClearStreamTargets();
    sprintf(buf, "vb_wc_%03d", vbId);
    NVDataNode* soTarget = dynamic_cast<NVDataNode*>(NVNAMESPACE->GetByName(buf)); assert(soTarget);
    m_gen_vertices_pass->AppendStreamTargets( soTarget );
    sprintf(buf, "vb_wn_%03d", vbId);
    soTarget = dynamic_cast<NVDataNode*>(NVNAMESPACE->GetByName(buf)); assert(soTarget);
    m_gen_vertices_pass->AppendStreamTargets( soTarget );
      if (m_nFrame < 16) { ((NRenderDX*)(m_viewer->GetRenderer()))->BeginStreamOutQuery("x"); }
    m_viewer->Render(m_gen_vertices_pass, false);
      if (m_nFrame < 16) { int nOut = ((NRenderDX*)(m_viewer->GetRenderer()))->EndStreamOutQuery("x"); char buf[64]; sprintf(buf, "m_gen_vertices_pass: output=%d\n", nOut); NOutputString(buf); }

    sprintf(buf, "idx_%03d", vbId);
    soTarget = dynamic_cast<NVDataNode*>(NVNAMESPACE->GetByName(buf)); assert(soTarget);
    m_gen_indices_pass->ClearStreamTargets();
    m_gen_indices_pass->AppendStreamTargets( soTarget );
      if (m_nFrame < 16) { ((NRenderDX*)(m_viewer->GetRenderer()))->BeginStreamOutQuery("x"); }
    m_viewer->Render(m_gen_indices_pass, false);
      if (m_nFrame < 16) { int nOut = ((NRenderDX*)(m_viewer->GetRenderer()))->EndStreamOutQuery("x"); char buf[64]; sprintf(buf, "m_gen_indices_pass: output=%d\n", nOut); NOutputString(buf); }
  }
  
  return nCells;
}

void TerrainDemo::SortChunks(td_chunkinfo** data, int nValues)
{
  // merge sort a list of chunks by .dist_from_cam

  assert(nValues > 0);
  if (nValues==1) 
    return;

  int mid = nValues/2;
  SortChunks(data, mid);
  SortChunks(&data[mid], nValues-mid);
  
  // now interleave the two sorted sets...
  td_chunkinfo** temp = m_tempChunks;
  int i = 0;
  int j = mid;
  int k = 0;
  while (i < mid && j < nValues) {
    if (data[i]->dist_from_cam < data[j]->dist_from_cam)
      temp[k++] = data[i++];
    else
      temp[k++] = data[j++];
  }
  while (i < mid)
    temp[k++] = data[i++];
  while (j < nValues)
    temp[k++] = data[j++];
  
  // copy back to the original array
  for (i=0; i<nValues; i++)
    data[i] = temp[i];
}

NPnt3f TerrainDemo::GetGeomCenter(int lod) 
{
  // Given the current camera position and the LOD you're interested in, 
  // this function returns the coordinates around which our current 11x11x11
  // array of chunks should be centered.  (If they were just centered at the
  // viewer, that would be dumb, because half of the chunks would be behind
  // you, not visible.)
  NVBaseCamera* cam = m_primaryPass->GetCamera();
  NPnt3f camPos = cam->GetGlobalPosition();
  NVec3f camDir = -cam->GetGlobalDirection();  //weird - camDir appears to be inverted!
  NPnt3f geomCenter;
  geomCenter.x = camPos.x + camDir.x*(float)(m_wsChunkSize[lod]*CHUNKS_PER_DIM)*0.5f;
  geomCenter.y = camPos.y + camDir.y*(float)(m_wsChunkSize[lod]*CHUNKS_PER_DIM)*0.5f;
  geomCenter.z = camPos.z + camDir.z*(float)(m_wsChunkSize[lod]*CHUNKS_PER_DIM)*0.5f;
  return geomCenter;
}

void ChunkIndexToWSxyz(int i, int j, int k, NPnt3i centerChunkPosInt, int WS_CHUNK_SIZE, int ib, int jb, int kb, int *x, int *y, int *z)
{
  int i2 = i-CHUNKS_PER_DIM/2;
  int j2 = j-CHUNKS_PER_DIM/2;
  int k2 = k-CHUNKS_PER_DIM/2;
  int i3 = i2 + centerChunkPosInt.x;
  int j3 = j2 + centerChunkPosInt.y;
  int k3 = k2 + centerChunkPosInt.z;
  int i4 = (i < SmartMod(centerChunkPosInt.x - CHUNKS_PER_DIM/2, CHUNKS_PER_DIM)) ? 1 : 0;
  int j4 = (j < SmartMod(centerChunkPosInt.y - CHUNKS_PER_DIM/2, CHUNKS_PER_DIM)) ? 1 : 0;
  int k4 = (k < SmartMod(centerChunkPosInt.z - CHUNKS_PER_DIM/2, CHUNKS_PER_DIM)) ? 1 : 0;
  int i5 = ib + i + CHUNKS_PER_DIM*i4; //i4 is 0 or 1
  int j5 = jb + j + CHUNKS_PER_DIM*j4; //i4 is 0 or 1
  int k5 = kb + k + CHUNKS_PER_DIM*k4; //i4 is 0 or 1
  assert(SmartMod(i5, CHUNKS_PER_DIM) == i);
  assert(SmartMod(j5, CHUNKS_PER_DIM) == j);
  assert(SmartMod(k5, CHUNKS_PER_DIM) == k);
  // now produce the WS x,y,z coords of the lower-left corner of the chunk:
  *x = i5*WS_CHUNK_SIZE;
  *y = j5*WS_CHUNK_SIZE;
  *z = k5*WS_CHUNK_SIZE;
}

void TerrainDemo::OnRenderPass(NVViewer* v, bool bStarting, NVRenderPass* rp)
{
  std::string passName = rp->GetName();

  if (passName == "build_density_pass" && bStarting && !m_bFreezeTess && !m_bDrawingTiledScreenShot) {
    // note that the 3 passes are always inactive, but we still get these notifications.
    // at this time we spoof the targets & call pass->render as much as we want!

    int nWorkThisFrame = 0;

    NVBaseCamera* cam = m_primaryPass->GetCamera();
    NVec3f camPos = PntToVec(cam->GetGlobalPosition());
    NVec3f camDir = -cam->GetGlobalDirection();  //kiv!

    m_nSortedChunks = 0;

    for (int lod=0; lod<NUM_LOD; lod++)
    {
      // Neat trick: make the chunks that are about 1 chunk in the highest priority ones.
      NVec3f hackedCamPos = camPos + camDir*(float)m_wsChunkSize[lod]*1.0f;

      int WS_CHUNK_SIZE = m_wsChunkSize[lod];//(4 >> lod);

      NPnt3f centerChunkPos = GetGeomCenter(lod);
      NPnt3i centerChunkPosInt = NPnt3i( SmartDiv(RoundUp(centerChunkPos.x), WS_CHUNK_SIZE), 
                                         SmartDiv(RoundUp(centerChunkPos.y), WS_CHUNK_SIZE), 
                                         SmartDiv(RoundUp(centerChunkPos.z), WS_CHUNK_SIZE)
                               );

      int ib = SmartDiv(0-CHUNKS_PER_DIM/2 + centerChunkPosInt.x, CHUNKS_PER_DIM)*CHUNKS_PER_DIM;
      int jb = SmartDiv(0-CHUNKS_PER_DIM/2 + centerChunkPosInt.y, CHUNKS_PER_DIM)*CHUNKS_PER_DIM;
      int kb = SmartDiv(0-CHUNKS_PER_DIM/2 + centerChunkPosInt.z, CHUNKS_PER_DIM)*CHUNKS_PER_DIM;

      for (int i=0; i<CHUNKS_PER_DIM; i++) {
        for (int j=0; j<CHUNKS_PER_DIM; j++) {
          for (int k=0; k<CHUNKS_PER_DIM; k++) {

            // check if the camera is near this chunk
            // & if we need to generate some new data!
            
            // camPosInt +1 causes one of the 8 i's to go up by 8; other 7 don't change.
            int x,y,z;
            ChunkIndexToWSxyz(i,j,k,centerChunkPosInt,WS_CHUNK_SIZE,ib,jb,kb, &x, &y, &z);
            
            //kiv/fixme: bbox sloppiness here
            NVec3f wsChunkLLPos     = NVec3f( (float)x - WS_CHUNK_SIZE*0.5f, (float)y - WS_CHUNK_SIZE*0.5f, float(z) - WS_CHUNK_SIZE*0.5f );
            NVec3f wsChunkCenterPos = NVec3f( (float)x + WS_CHUNK_SIZE*0.5f, (float)y + WS_CHUNK_SIZE*0.5f, float(z) + WS_CHUNK_SIZE*0.5f );
            NVec3f wsChunkURPos     = NVec3f( (float)x + WS_CHUNK_SIZE*1.5f, (float)y + WS_CHUNK_SIZE*1.5f, float(z) + WS_CHUNK_SIZE*1.5f );
            bool bViewCull = (m_bViewCull) ? BBoxIsOutsideViewFrustum( VecToPnt(wsChunkLLPos), VecToPnt(wsChunkURPos), cam )
                                           : false;

            td_chunkinfo* ci = &(m_chunks[lod][i][j][k]);
            if (bViewCull || ci->x != x || ci->y != y || ci->z != z)
              ClearChunk(lod,i,j,k);  // this also marks the VB as free (if it was using one)
            ci->x = x;
            ci->y = y;
            ci->z = z;
           
            if (bViewCull)
              ci->dist_from_cam = 9999.9f; 
            else {
              NVec3f vCamToChunk       = wsChunkCenterPos - (camPos      );// + camDir*m_wsChunkSize[lod]*2.0f);
              NVec3f vHackedCamToChunk = wsChunkCenterPos - (hackedCamPos);// + camDir*m_wsChunkSize[lod]*2.0f);

			  //TWEAK
			  float far_lo_block_prioritization = 1.3f;  // anything >1 boosts their priority

              // both of these get fit into the [0..1] range:
              // (and norm_dist is based on different scales for the diff lod's! - relative)
              float aligned_with_cam_dir_ness = dot(normalize(vCamToChunk), camDir)*0.5f + 0.5f;
              float norm_dist = magnitude(vHackedCamToChunk) / (float)(powf(m_wsChunkSize[lod],far_lo_block_prioritization) * CHUNKS_PER_DIM);
              
              float       aligned_importance = 0.8f;
              const float dist_importance    = 0.5f;  // must be same for all LOD's.
              switch(lod) {
                case 0:
                  // highest LOD
                  aligned_importance = 0.7f;
                  norm_dist *= 1.1f;
                  break;
                case 1:
                  aligned_importance = 0.63f;
                  //norm_dist *= 1.5f; // so lo-res chunks generate a little earlier than hi-res chunks
                  break;
                case 2:
                  aligned_importance = 0.4f;
                  //norm_dist *= 2.1f; // so lo-res chunks generate a little earlier than hi-res chunks
                      // bigger mult  -> more chunks are lo-res, farther range, fewer hi-res close chunks
                      // smaller mult -> lots of hi-res chunks, low range out in the distance.
                  break;
              }
              ci->dist_from_cam = lerp(0.0f, norm_dist                  , dist_importance   ) 
                                * lerp(0.5f, 1-aligned_with_cam_dir_ness, aligned_importance);

  
              // finally, if a neighbor chunk is known to have polygons on it,
              // super-prioritize this chunk (because it will have polygons, for sure).
              // (~surface crawling)
              bool bNeighborHasPolys = 
                (m_chunks[lod][i+1][j][k].x == x+1 && m_chunks[lod][i+1][j][k].bHasPolys == TB_TRUE) ||
                (m_chunks[lod][i-1][j][k].x == x-1 && m_chunks[lod][i-1][j][k].bHasPolys == TB_TRUE) ||
                (m_chunks[lod][i][j+1][k].y == y+1 && m_chunks[lod][i][j+1][k].bHasPolys == TB_TRUE) ||
                (m_chunks[lod][i][j-1][k].y == y-1 && m_chunks[lod][i][j-1][k].bHasPolys == TB_TRUE) ||
                (m_chunks[lod][i][j][k+1].z == z+1 && m_chunks[lod][i][j][k+1].bHasPolys == TB_TRUE) ||
                (m_chunks[lod][i][j][k-1].z == z-1 && m_chunks[lod][i][j][k-1].bHasPolys == TB_TRUE);
              if (bNeighborHasPolys)
                ci->dist_from_cam *= 0.7f;
              

			  
              m_sortedChunks[ m_nSortedChunks++ ] = ci;
            }
          }
        }
      }
    }

    // now sort the chunks for this LOD by distance from the camera.
    if (m_nSortedChunks > 0)
      SortChunks(m_sortedChunks, m_nSortedChunks);

    // Adjust these values to determine the max # of empty chunks and nonempty chunks
    // we're allowed to generate in a single frame.  Keeps the workload (and framerate)
    // smooth.
    #define MAX_WORK_PER_FRAME       11 //TWEAK
    #define WORK_FOR_EMPTY_CHUNK     1
    #define WORK_FOR_NONEMPTY_CHUNK  4

    // now take the closest N chunks (?) and make sure their VBs are ready (gen some).
    int worst_chunk_that_might_have_a_vb_assigned = m_nSortedChunks-1;
    int numChunksToDrawThisLOD = m_nSortedChunks;//min(m_nSortedChunks[lod], m_num_vb[lod]);
    for ( int n=0; n<numChunksToDrawThisLOD && 
                   nWorkThisFrame < MAX_WORK_PER_FRAME - WORK_FOR_NONEMPTY_CHUNK; 
          n++) 
    {
      td_chunkinfo* ci = m_sortedChunks[n];
      int knownFreeVB = ci->vbId;
      //if (FRAND < 0.05f) 
      //  ClearChunk(lod, ci->i, ci->j, ci->k); //FIXME
      if (ci->vbId == -1 && ci->bHasPolys==TB_UNKNOWN) {
        // BUILT THIS LOD FOR THIS CHUNK, STORE IN A VB, AND REMEMBER THE VB #.
        int vb = -1;
        if (m_num_free_vbs==0) {
          // no more VBs?  Find the furthest chunk that has a VB and steal its VB...
          while (worst_chunk_that_might_have_a_vb_assigned > n) {
            td_chunkinfo* ci_to_kill = m_sortedChunks[worst_chunk_that_might_have_a_vb_assigned];
            worst_chunk_that_might_have_a_vb_assigned--;
            if (ci_to_kill->vbId >= 0) {
              // free it up! and use its vb.
              vb = ci_to_kill->vbId;
              ci_to_kill->vbId = -1; 
              break;
            }
          }
          if (vb == -1) {
            // give up - we've saturated all of our VBs.
            m_nSortedChunks = n;  // but remember the # of good ones, for OnDraw
            break;  
          }
        }
        else 
          vb = m_free_vb_list[ --m_num_free_vbs ];//GetFreeVB();
        assert(m_num_free_vbs >= 0);
        assert(vb >= 0);
        assert(vb < NUM_VB);
        //m_vb_is_taken[vb] = true;

        NVec3f wsChunkPos = NVec3f( (float)(ci->x), (float)(ci->y), (float)(ci->z) );
        int nCells = BuildLodChunkNOW(wsChunkPos, ci->lod, vb);
        ci->vbId = vb;
        if (nCells > 0) {
          ci->bHasPolys = TB_TRUE;
          //char buf[256];
          //sprintf(buf, "frame %d: chunk %d %d %d with %d polys.\n", m_nFrame, ci->x, ci->y, ci->z, nCells);
          //NOutputString(buf);
        }
        else {
          ClearChunk(ci->lod,ci->i,ci->j,ci->k);  //frees up the VB
          ci->bHasPolys = TB_FALSE;
        }

        //char buf[256];
        //sprintf(buf, "generating chunk @ %d,%d,%d - dist=%f\n", ci->x,ci->y,ci->z, ci->dist_from_cam);
        //NOutputString(buf);

        if (nCells > 0)
          nWorkThisFrame += WORK_FOR_NONEMPTY_CHUNK;
        else
          nWorkThisFrame += WORK_FOR_EMPTY_CHUNK;
      }
    }
  }

  // for the first few frames, track # of streamed out vertices for all passes...
  /*
  if (m_nFrame < 4) {
    if (bStarting)
      ((NRenderDX*)(v->GetRenderer()))->BeginStreamOutQuery(rp->GetName());
    else {
      int x = ((NRenderDX*)(v->GetRenderer()))->EndStreamOutQuery(rp->GetName());
      if (x > 0) {
        char buf[256];
        sprintf(buf, "frame %d: pass %s streamed out %d vertices\n", m_nFrame, rp->GetName().c_str(), x);
        NOutputString(buf);
      }
    }
  }*/
}

void TerrainDemo::OnDraw(NVViewer* v, NVRenderSettings* rs, NVShape* s)
{
  // This function is called whenever the engine is about to draw a Shape.
  // For this demo, we have the engine draw a dummy shape for each LOD of the rock,
  //   and in this callback here, we manually draw all the chunks we want to draw 
  //   instead.

  int lod = -1;
  bool bDrawOpaque = false;
  bool bDrawTranspar = false;
  if (s->GetName()=="draw_rock_hi_opaque_shape") {
    lod = 0;
    bDrawOpaque = true;
  }
  else if (s->GetName()=="draw_rock_med_opaque_shape") {
    lod = 1;
    bDrawOpaque = true;
  }
  else if (s->GetName()=="draw_rock_lo_shape") {
    lod = 2;
    bDrawOpaque = true;
    bDrawTranspar = true;
  }
  else if (s->GetName()=="draw_rock_med_alpha_shape") {
    lod = 1;
    bDrawTranspar = true;
  }
  else if (s->GetName()=="draw_rock_hi_alpha_shape") {
    lod = 0;
    bDrawTranspar = true;
  }
  
  if (lod >= 0 && lod < NUM_LOD)
  {
    // draw a bunch of chunks at the desired LOD.

    NVShader* v_shader;
    switch(lod) {
      case 0: v_shader = (NVShader*)(NVNAMESPACE->GetByName("draw_rock_hi_v")); break;
      case 1: v_shader = (NVShader*)(NVNAMESPACE->GetByName("draw_rock_med_v")); break;
      case 2: v_shader = (NVShader*)(NVNAMESPACE->GetByName("draw_rock_lo_v")); break;
    }

    NVFloatParam* fp = dynamic_cast<NVFloatParam*>(m_ChunkCB->FindParam("opacity"));
    assert(fp);
    NVBufferParam* bp_wc = dynamic_cast<NVBufferParam*>(v_shader->FindParam("vb_worldCoordAmbo")); 
    assert(bp_wc);
    NVBufferParam* bp_wn = dynamic_cast<NVBufferParam*>(v_shader->FindParam("vb_worldNorm")); 
    assert(bp_wn);

    // draw all chunks (in the desired LOD and of the desired opaque 
    //  vs. alpha-fading status) that actually have a VB.
    int numChunksToDrawThisLOD = m_nSortedChunks;//min(m_nSortedChunks, NUM_VB);//min(m_nSortedChunks[lod], m_num_vb[lod]);
    for (int n=0; n<numChunksToDrawThisLOD; n++) {
      td_chunkinfo* ci = m_sortedChunks[n];
      if (ci->vbId >= 0 && ci->lod == lod) { 

        if (ci->opacity >= 1.0f && !bDrawOpaque)
          continue;
        if (ci->opacity < 1.0f && !bDrawTranspar)
          continue;

        NVGeometry* g = m_ib_list[ci->vbId];//(NVGeometry*)(NVNAMESPACE->GetByName("vb_hi_000")); 
        assert(g); 

        // update opacity param
        fp->SetValue(NPnt4f(ci->opacity,0,0,0));
        m_ChunkCB->Update(*m_viewer->GetViewerInfo(), NULL);

        //HOOK UP the correct wsCoordAmbo and wsNormal streams for input...
        bp_wc->SetGeometry( m_vb_list[ci->vbId] );
        bp_wn->SetGeometry( m_vb_list[ci->vbId] );

        // DRAW IT
        bp_wc->Apply(NR_VERTEX_SHADER, v_shader->GetShader()->GetResourceBindings());
        bp_wn->Apply(NR_VERTEX_SHADER, v_shader->GetShader()->GetResourceBindings());
        //rs->Apply(...
        g->Draw( m_viewer, s->GetRenderSettings(), 1, NULL);
        ci->opacity = min(1.0f, ci->opacity + (float)m_dt_pausable / (float)(CHUNK_FADEIN_SECONDS));
      }
    }
  }
}

void TerrainDemo::Quit() {

  assert(s_instance != NULL);
  delete s_instance;

  exit(0);
}

void TerrainDemo::MainLoop(){
  m_viewer->GetRenderer()->GetWindow()->MainLoop();
}


void TerrainDemo::Pause(bool pause, bool force){

  if (!force && m_Paused == pause)
    return;

  m_Paused = pause;  

  NVOnOffButton *button = dynamic_cast<NVOnOffButton*>(NVNAMESPACE->GetByName("pause_button"));
  if (button != NULL)
    button->SetState(m_Paused);

  // pause/unpause audio:
  int N = m_sounds.size();
  for (int i=0; i<N; i++) {
    if (m_Paused)
      m_sounds[i]->Pause();
    else
      m_sounds[i]->Unpause();
  }
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

  timeBeginPeriod(1);

  if (!strcmp("_DXSDK_PRODUCT_MINOR", "08")) {
    MessageBox(NULL, "you built this with the wrong dx9 sdk!\nPoint Dev Studio to the august 2005 dx9 sdk and rebuild all.", "ERROR", MB_OK|MB_SETFOREGROUND|MB_TOPMOST);
    exit(6);
  }  

  NOutputString("command line: ");
  NOutputString(lpCmdLine);
  NOutputString("\n");

  if (::FindWindow(CLASS_NAME, WINDOW_TITLE)) {
    NOutputString("  TerrainDemo: EXITING NEW INSTANCE b/c another window is already open!");
    exit(5);
  }

  // check windows version!
  DWORD dwVersion = GetVersion();
  if (dwVersion & 0x80000000) {
    // we're not on 2000 or XP or later... exit!
    MessageBox(NULL, "Windows 2000, XP, or later is required\nto run this demo.", "ERROR", MB_OK|MB_SETFOREGROUND|MB_TOPMOST);
    exit(6);
  }

  g_hInstance = hInstance;

  // make the test object
  TerrainDemo *testObject = new TerrainDemo(lpCmdLine, hInstance);
  
  // go into its main loop
  testObject->MainLoop();
  
  // <that's it - code never reaches this point...>

  return 0;
}

void TerrainDemo::LoadConfig()
{
  // this loads from the userOverrides text file

  std::ifstream userOverrides;
  userOverrides.open("userOverrides.txt");

  uint customWidth = 0;
  uint customHeight = 0;
  uint customAA = 0;
  uint customAF = 0;
  bool customVsync = false;
  bool customShowHelp = false;

  if (userOverrides.is_open())
  {
    // useDefaults
    std::string boolString;
    userOverrides >> boolString;
    if (boolString == "true")
    {
      m_bUserOverride = false;
    }
    else
    if (boolString == "false")
    {
      m_bUserOverride = true;
    }
    else
    {
      // something's wrong.
      userOverrides.close();
      return;
    }
    boolString.clear();

    // resolution
    uint width = 0;
    uint height = 0;
    userOverrides >> width >> height;
    if (width > 0 && height > 0)
    {
      customWidth = width;
      customHeight = height;
    }
    else
    {
      // something's wrong.
      userOverrides.close();
      return;
    }

    // aa
    uint aa = 0;
    userOverrides >> aa;
    if (aa > 0)
    {
      customAA = aa;
    }
    else
    {
      // something's wrong.
      userOverrides.close();
      return;
    }

    // af
    uint af = 0;
    userOverrides >> af;
    if (af > 0)
    {
      customAF = af;
    }
    else
    {
      // something's wrong.
      userOverrides.close();
      return;
    }

    // vsync
    userOverrides >> boolString;
    if (boolString == "true")
    {
      customVsync = true;
    }
    else
    if (boolString == "false")
    {
      m_bVSync = false;
    }
    else
    {
      // something's wrong.
      userOverrides.close();
      return;
    }
    boolString.clear();

    // show help
    userOverrides >> boolString;
    if (boolString == "true")
    {
      customShowHelp = true;
    }
    else
    if (boolString == "false")
    {
      customShowHelp = false;
    }
    else
    {
      // something's wrong.
      userOverrides.close();
      return;
    }
    boolString.clear();

    userOverrides.close();

    // apply these settings?
    if (m_bUserOverride || m_bConfigMode)
    {
      // If userOverride is on, obviously override these now.
      // But even if it's not, when you are opening the config
      // box, you want to see what you put there previously.
      m_winDimensions.x = m_renDimensions.x = customWidth;
      m_winDimensions.y = m_renDimensions.y = customHeight;
      m_numSamples = customAA;
      //m_fLichenAniso = customAF;
      m_bVSync = customVsync;
      m_bHelpButton = customShowHelp;
    }
  }
}



void TerrainDemo::UpdateHelpScreen()
{
  m_fHelpScrOpacity -= (float)m_dt_unpausable;

  // to draw or not to draw?
  NVShape* shape = dynamic_cast<NVShape*>(NVNAMESPACE->GetByName("helpscr_shape")); assert(shape);
  shape->SetCull( (m_fHelpScrOpacity >= 0.001f) ? NV_CULL_NEVER : NV_CULL_ALWAYS );
  NVFloatParam* fp = dynamic_cast<NVFloatParam*>(shape->FindParam(NR_FRAGMENT_SHADER,"opacity")); assert(fp);

  // fade it in/out
  float t = saturate(m_fHelpScrOpacity);
  if (m_fHelpScrOpacity > SECONDS_TO_SHOW_HELP_SCREEN - SECONDS_TO_FADE_IN_HELP_SCREEN)
    t = (SECONDS_TO_SHOW_HELP_SCREEN - m_fHelpScrOpacity)/(SECONDS_TO_FADE_IN_HELP_SCREEN);
  fp->SetValue(NPnt4f(t,0,0,0));
}

void TerrainDemo::UpdateAboutBox()
{
  m_fAboutBoxOpacity -= (float)m_dt_unpausable;

  // to draw or not to draw?
  NVShape* shape = dynamic_cast<NVShape*>(NVNAMESPACE->GetByName("aboutbox_shape")); assert(shape);
  shape->SetCull( (m_fAboutBoxOpacity >= 0.001f) ? NV_CULL_NEVER : NV_CULL_ALWAYS );
  NVFloatParam* fp = dynamic_cast<NVFloatParam*>(shape->FindParam(NR_FRAGMENT_SHADER,"opacity")); assert(fp);

  // fade it in/out
  float t = saturate(m_fAboutBoxOpacity);
  if (m_fAboutBoxOpacity > SECONDS_TO_SHOW_ABOUT_BOX - SECONDS_TO_FADE_IN_ABOUT_BOX)
    t = (SECONDS_TO_SHOW_ABOUT_BOX - m_fAboutBoxOpacity)/(SECONDS_TO_FADE_IN_ABOUT_BOX);
  fp->SetValue(NPnt4f(t,0,0,0));
}





// take a big screen capture using tiling
NImage* TerrainDemo::ScreenShotTiled(NVRenderPass *renderPass, int tile_w, int tile_h, int rows, int cols, int channels)
{
  NArray<uchar> data(tile_w*rows*tile_h*cols*channels);
  NPixelType ptype;
  switch(channels){
  case 1:  ptype = NPT_L;    break;
  case 3:  ptype = NPT_BGR;  break;
  case 4:  ptype = NPT_BGRA; break;
  default: ptype = NPT_BGRA; break;
  };
  NImage *bigImage = new NImage(ptype, tile_w*rows, tile_h*cols, data, 1);

  // build list of other cameras to fudge during tiled screenshots.
  // main (near) renderpass uses maincamNearShape... but we also want maincamFarShape
  std::vector<NVPerspectiveCamera*> extra_cameras_to_hack;
  //NVPerspectiveCamera *cam = dynamic_cast<NVPerspectiveCamera*>(NVNAMESPACE->GetByName("maincamFarShape"));
  //assert(cam);
  //extra_cameras_to_hack.push_back(cam);
  //extra_cameras_to_hack.push_back(m_userCamera);  // also update maincamShape

  // disable menus, logos, etc:
  //bool bMainMenuWasVisible = m_mainMenu ? m_mainMenu->IsVisible() : false;
  //if (m_mainMenu) 
  //  m_mainMenu->SetVisible(false);
  //if (m_sliderMenu)
  //    m_sliderMenu->SetVisible(false);
  float old_parallax_distance = 1;
  if (1)  // DO THESE EVEN IF NOT TILING:
  {
    // hide logo badge
    NVShape *s = dynamic_cast<NVShape*>(NVNAMESPACE->GetByName("nvLogo_quadProxy_shape")); assert(s);
    NVFloatParam *cp = dynamic_cast<NVFloatParam*>(s->GetFragmentParam("opacity")); assert(cp);
    cp->SetValue(NPnt4f(0,0,0,0));   // .x = logo badge opacity

    // hide 'click here for help' msg
    s = dynamic_cast<NVShape*>(NVNAMESPACE->GetByName("clickhelp_shape"));
    cp = dynamic_cast<NVFloatParam*>(s->GetFragmentParam("opacity"));
    cp->SetValue(NPnt4f(0,0,0,0));
  }

  float old_glow_str[2] = { 1, 1 };
  float affect_amt[] = { 1, 1, 0, 0, 0, 0, };
  std::string water_size_param_names[] = { 
    "smallMistSize", 
    "bigMistSize", 
    "particleStreakWidth",
    "particleStreakLength",
    "particleSlidingWidth",
    "particleSlidingLength"
  };
  if (tile_w > 1) {
    // turn off 'glow' effect (it'll have to be recreated in photoshop)
    NVShader *s = dynamic_cast<NVShader*>(NVNAMESPACE->GetByName("composite_f")); assert(s);
    NVFloatParam *cp = dynamic_cast<NVFloatParam*>(s->FindParam("glow_str")); assert(cp);
    old_glow_str[0] = cp->GetValue().x;
    cp->SetValue(NPnt4f(0,0,0,0));
    
    s = dynamic_cast<NVShader*>(NVNAMESPACE->GetByName("downsample_glow_f")); assert(s);
    cp = dynamic_cast<NVFloatParam*>(s->FindParam("glow_str")); assert(cp);
    old_glow_str[1] = cp->GetValue().x;
    cp->SetValue(NPnt4f(0,0,0,0));
  }

  // grab tiles!
  float v_range = m_renDimensions.y/(float)m_winDimensions.y;
  float du = 1.0f / (float) cols;
  float dv = 1.0f / (float) rows;// * v_range;
  for(int y=0; y<rows; y++) {
    for(int x=0; x<cols; x++) {
      float u = du * x;
      float v = dv * y;// + 0.5f*(1-v_range);

      // grab tile from the screen
      NImage *image = m_viewer->GrabTile(renderPass, tile_w, tile_h, u, v, u+du, v+dv, extra_cameras_to_hack);

      // insert it into the big image
      bigImage->Insert(*image, x*tile_w, y*tile_h);
      delete image;
    }
  }

  if (tile_w > 1) 
  {
    // turn glow/blur back on
    NVShader *s = dynamic_cast<NVShader*>(NVNAMESPACE->GetByName("composite_f")); assert(s);
    NVFloatParam *cp = dynamic_cast<NVFloatParam*>(s->FindParam("glow_str")); assert(cp);
    cp->SetValue(NPnt4f(old_glow_str[0],0,0,0));

    s = dynamic_cast<NVShader*>(NVNAMESPACE->GetByName("downsample_glow_f")); assert(s);
    cp = dynamic_cast<NVFloatParam*>(s->FindParam("glow_str")); assert(cp);
    cp->SetValue(NPnt4f(old_glow_str[1],0,0,0));
  }

  // restore logos, menus, etc
  //if (bMainMenuWasVisible) 
  //  m_mainMenu->SetVisible(true);
  if (1)  // DO THESE EVEN IF NOT TILING:
  {
    // hide logo badge
    NVShape *s = dynamic_cast<NVShape*>(NVNAMESPACE->GetByName("nvLogo_quadProxy_shape")); assert(s);
    NVFloatParam *cp = dynamic_cast<NVFloatParam*>(s->GetFragmentParam("opacity")); assert(cp);
    cp->SetValue(NPnt4f(1,0,0,0));   // .x = logo badge opacity

    // hide 'click here for help' msg
    s = dynamic_cast<NVShape*>(NVNAMESPACE->GetByName("clickhelp_shape"));
    cp = dynamic_cast<NVFloatParam*>(s->GetFragmentParam("opacity"));
    cp->SetValue(NPnt4f(m_bHelpButton ? 1.0f : 0,0,0,0));
  }

  return bigImage;
}

void TerrainDemo::ScreenShot(int tiling)
{
  bool bWasPaused = m_Paused;

  if (tiling > 1) {
    m_bDrawingTiledScreenShot = true;
    if (!m_Paused)
      Pause(true);
    //UpdateFlocking();  // call this an extra time so LOD's crank way up.
  }

  char  filename[128];//, filename_mask[128], filename_metadata[128];
  static int screenshotCount = 0;

  // Turn off some rendering
  //bool menuIsVisible = m_animManager->m_topLevelMenu->IsVisible();
  //bool warnOnRefErr  = NVField::WarnOnRefError();
  //if (menuIsVisible)
  //  m_animManager->m_topLevelMenu->SetVisible(false);
  //if (warnOnRefErr)
  //  NVField::WarnOnRefError(false);
  
  // find a suitable filename
  bool  found = false;      
  while(found==false){
    sprintf(filename, "cap%.8d.tga", screenshotCount);
    FILE* F=fopen(filename, "r");
    if (!F)
      found = true;
    else
      fclose(F);
    screenshotCount++;
  }

  // 1. Grab the screenshot and write it out
  NRecti winSize = m_primaryPass->GetViewport();
  if (tiling==1)
    SaveFrameBuffer(filename);
  else {
    NImage *img = ScreenShotTiled(m_primaryPass, winSize.width, winSize.height, tiling, tiling, 3);
    WriteTGAFile(filename, *img, "");
    delete img;
  }

  if (!bWasPaused) 
    Pause(false);

  m_bDrawingTiledScreenShot = false;
}

void TerrainDemo::SaveFrameBuffer(const std::string& filename)
{
  NRender* renderer = m_viewer->GetRenderer();
  NRecti vp = renderer->GetViewport();

  uint width = vp.width;
  uint height = vp.height;
  NArray<uchar> data(4*width*height);
  NImage image(NPT_BGRA, width, height, data, 1);

  renderer->Read(image, NPnt2i(0,0), false);

  WriteImage(filename.c_str(), image);

}

void TerrainDemo::Randomize()
{
  int x = rand();
  char buf[256];
  sprintf(buf, "Randomize(): seed is %d\n", x);
  NOutputString(buf);
  srand(x);

  // randomize the noise octave orientations
  NVMayaTransform* t;
  {
    std::string transform_name[] = {
      "xform_octave0",
      "xform_octave1",
      "xform_octave2",
      "xform_octave3",
      "xform_octave4",
      "xform_octave5",
      "xform_octave6",
      "xform_octave7",
    };
    for (int i=0; i<sizeof(transform_name)/sizeof(transform_name[0]); i++) 
    {
      t = dynamic_cast<NVMayaTransform*>(NVNAMESPACE->GetByName(transform_name[i])); 
      assert(t);
      t->SetRotation( NVec3f( FRAND*6.28f-3.14f, FRAND*6.28f-3.14f, FRAND*6.28f-3.14f ) );
      t->SetTranslation( NVec3f( FRAND*4-2, FRAND*4-2, FRAND*4-2 ) );  //kiv:ok?
      //t->SetScale( NVec3f( 1,1,1 ));
    }
  }
 
}
